diff --git a/packages/oneclient_app/assets/icons/icon-logo.svg b/packages/oneclient_app/assets/icons/icon-logo.svg
new file mode 100644
index 00000000..84f43670
--- /dev/null
+++ b/packages/oneclient_app/assets/icons/icon-logo.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/oneclient_app/assets/icons/line-chart-up-01.svg b/packages/oneclient_app/assets/icons/line-chart-up-01.svg
new file mode 100644
index 00000000..6b077ab1
--- /dev/null
+++ b/packages/oneclient_app/assets/icons/line-chart-up-01.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/oneclient_app/src/components/grid_columns.rs b/packages/oneclient_app/src/components/grid_columns.rs
deleted file mode 100644
index a5a1b3c1..00000000
--- a/packages/oneclient_app/src/components/grid_columns.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-use freya::prelude::*;
-
-use super::Dropdown;
-
-pub const GRID_COLUMNS_MIN: u8 = 1;
-pub const GRID_COLUMNS_MAX: u8 = 5;
-
-pub fn resolved_columns(columns: u8) -> usize {
- columns.clamp(GRID_COLUMNS_MIN, GRID_COLUMNS_MAX) as usize
-}
-
-pub fn grid_columns_picker(mut columns: State, height: f32) -> impl IntoElement {
- let options: Vec = (GRID_COLUMNS_MIN..=GRID_COLUMNS_MAX)
- .map(|n| n.to_string())
- .collect();
- let selected = resolved_columns(*columns.read()).to_string();
-
- Dropdown::new(selected, options)
- .width(Size::px(60.))
- .height(Size::px(height))
- .on_select(move |idx: usize| columns.set(GRID_COLUMNS_MIN + idx as u8))
-}
-
-pub fn cell_width(total: f32, cols: usize, gap: f32) -> f32 {
- let cols = cols.max(1);
- ((total - (cols - 1) as f32 * gap) / cols as f32).max(0.)
-}
diff --git a/packages/oneclient_app/src/components/icons.rs b/packages/oneclient_app/src/components/icons.rs
index 4eab4960..4847de28 100644
--- a/packages/oneclient_app/src/components/icons.rs
+++ b/packages/oneclient_app/src/components/icons.rs
@@ -115,9 +115,11 @@ pub enum IconType {
FolderDownload,
Globe01,
HelpCircle,
+ IconLogo,
InfoCircle,
Key01,
LayoutTop,
+ LineChartUp01,
Link03,
LinkExternal01,
Loading02,
diff --git a/packages/oneclient_app/src/components/mod.rs b/packages/oneclient_app/src/components/mod.rs
index 081cbfd5..de7cdae1 100644
--- a/packages/oneclient_app/src/components/mod.rs
+++ b/packages/oneclient_app/src/components/mod.rs
@@ -12,7 +12,6 @@ mod optional_mods_popup;
mod dropdown;
mod dynamic_art;
mod file_drop;
-mod grid_columns;
mod icons;
mod java_install_manager;
mod generic_prompt;
@@ -61,7 +60,6 @@ pub use optional_mods_popup::OptionalModsPopup;
pub use dropdown::Dropdown;
pub use dynamic_art::{ART_PREVIEW_EDGE, DynamicArt};
pub use file_drop::FileDropOverlay;
-pub use grid_columns::{cell_width, grid_columns_picker, resolved_columns};
pub use icons::{Icon, IconTint, IconType};
pub use java_install_manager::JavaInstallManager;
pub use generic_prompt::GenericPromptOverlay;
diff --git a/packages/oneclient_app/src/components/navbar/app_navbar.rs b/packages/oneclient_app/src/components/navbar/app_navbar.rs
index 32fd70ab..3be4be2f 100644
--- a/packages/oneclient_app/src/components/navbar/app_navbar.rs
+++ b/packages/oneclient_app/src/components/navbar/app_navbar.rs
@@ -16,12 +16,17 @@ use crate::{
};
const NAVBAR_INTRO_MS: u64 = 460;
+const LOGO_HIDE_NAVBAR_W: f32 = 1100.;
+const NAVBAR_SIDE_PADDING_PX: f32 = 40.;
+const NAV_LINK_SPACING_PX: f32 = 36.;
+const COMPACT_LOGO_PX: f32 = 28.;
#[derive(PartialEq)]
pub struct Navbar;
impl Component for Navbar {
fn render(&self) -> impl IntoElement {
+ let mut navbar_width = use_state(|| 0f32);
let intro = use_animation(|conf| {
conf.on_creation(OnCreation::Run);
AnimNum::new(0., 1.)
@@ -32,6 +37,9 @@ impl Component for Navbar {
let eased = intro.get().value();
let slide = (1.0 - eased) * -theme::NAVBAR_HEIGHT_PX;
+ let measured = *navbar_width.read();
+ let show_logo = measured <= 0. || measured > LOGO_HIDE_NAVBAR_W;
+
rect()
.width(Size::fill())
.height(Size::px(theme::NAVBAR_HEIGHT_PX))
@@ -44,11 +52,17 @@ impl Component for Navbar {
.horizontal()
.content(Content::Flex)
.cross_align(Alignment::Center)
- .padding(Gaps::new_symmetric(0.0, 40.0))
+ .padding(Gaps::new_symmetric(0.0, NAVBAR_SIDE_PADDING_PX))
.offset_y(slide)
.opacity(eased)
- .child(navbar_left())
- .child(navbar_center())
+ .on_sized(move |event: Event| {
+ let next = event.data().area.width();
+ if (*navbar_width.peek() - next).abs() > 0.5 {
+ navbar_width.set(next);
+ }
+ })
+ .child(navbar_left(show_logo))
+ .child(navbar_center(!show_logo))
.child(NavbarRight),
)
.child(
@@ -61,34 +75,60 @@ impl Component for Navbar {
}
}
-fn navbar_left() -> impl IntoElement {
+fn navbar_left(show_logo: bool) -> impl IntoElement {
rect()
.horizontal()
- .width(Size::flex(1.0))
+ .width(if show_logo {
+ Size::flex(1.0)
+ } else {
+ Size::auto()
+ })
.cross_align(Alignment::Center)
- .child(navbar_logo())
+ .maybe(!show_logo, |el| {
+ el.padding(Gaps::new(0., NAV_LINK_SPACING_PX, 0., 0.))
+ })
+ .child(if show_logo {
+ NavbarLogo.into_element()
+ } else {
+ Icon::new(IconType::IconLogo)
+ .size(COMPACT_LOGO_PX)
+ .into_element()
+ })
}
-fn navbar_logo() -> impl IntoElement {
- let bytes = use_memo(|| crate::AppAssets::get_bytes("logo.svg").unwrap_or_default());
+#[derive(PartialEq)]
+struct NavbarLogo;
- SvgViewer::new(("logo.svg", bytes.read().cloned()))
- .show_loader(false)
- .height(Size::px(44.))
- .width(Size::px(214.))
- .color(theme::colors::fg_primary())
+impl Component for NavbarLogo {
+ fn render(&self) -> impl IntoElement {
+ let bytes = use_memo(|| crate::AppAssets::get_bytes("logo.svg").unwrap_or_default());
+
+ SvgViewer::new(("logo.svg", bytes.read().cloned()))
+ .show_loader(false)
+ .height(Size::px(44.))
+ .width(Size::px(214.))
+ .color(theme::colors::fg_primary())
+ }
}
-fn navbar_center() -> impl IntoElement {
+fn navbar_center(is_small: bool) -> impl IntoElement {
let route = use_route::();
let browse_target = browse_target();
rect()
.horizontal()
.width(Size::flex(1.0))
- .main_align(Alignment::Center)
+ .main_align(if is_small {
+ Alignment::Start
+ } else {
+ Alignment::Center
+ })
.cross_align(Alignment::Center)
- .spacing(36.)
+ .spacing(if is_small {
+ NAV_LINK_SPACING_PX
+ } else {
+ NAV_LINK_SPACING_PX / 2.
+ })
.child(NavLink {
active: route == Route::Home {},
target: NavTarget::Route(Route::Home {}),
@@ -115,11 +155,6 @@ fn navbar_center() -> impl IntoElement {
target: NavTarget::Route(browse_target),
nav_label: "Browse",
})
- .child(NavLink {
- active: route == Route::Stats {},
- target: NavTarget::Route(Route::Stats {}),
- nav_label: "Stats",
- })
}
/// Falls back active cluster then most recently played then Versions when none exist
@@ -253,12 +288,22 @@ impl Component for NavbarRight {
let _ = RouterContext::get().push(Route::SettingsLauncher {});
};
+ let open_stats = |_| {
+ let _ = RouterContext::get().push(Route::Stats {});
+ };
+
rect()
.horizontal()
.width(Size::flex(1.0))
.main_align(Alignment::End)
.cross_align(Alignment::Center)
.spacing(8.)
+ .child(
+ super::navbar_button()
+ .tooltip("Stats")
+ .child(Icon::new(IconType::LineChartUp01).size(20.))
+ .on_press(open_stats),
+ )
.child(
super::navbar_button()
.tooltip("Notifications")
diff --git a/packages/oneclient_app/src/components/tab_bar.rs b/packages/oneclient_app/src/components/tab_bar.rs
index c56cb394..39679069 100644
--- a/packages/oneclient_app/src/components/tab_bar.rs
+++ b/packages/oneclient_app/src/components/tab_bar.rs
@@ -93,6 +93,7 @@ fn tab_label(text: &str, font_size: f32, active: bool) -> impl IntoElement {
.text(text.to_string())
.font_size(font_size)
.font_weight(ACTIVE_WEIGHT)
+ .max_lines(1)
.color(Color::TRANSPARENT)
.text_align(TextAlign::Center),
)
@@ -115,6 +116,7 @@ fn tab_label(text: &str, font_size: f32, active: bool) -> impl IntoElement {
} else {
FontWeight::NORMAL
})
+ .max_lines(1)
.color(colors::fg_primary())
.text_align(TextAlign::Center),
),
@@ -135,23 +137,36 @@ fn count_pill(count: &str, font_size: f32) -> impl IntoElement {
)
}
+fn min_tab_width(text: &str, font_size: f32) -> f32 {
+ text.chars().count() as f32 * font_size * 0.62
+}
+
impl IntoElement for TabBar {
fn into_element(self) -> Element {
let font_size = self.font_size;
+ let overflows = self.width == Size::auto();
+ let content = if overflows {
+ Content::Normal
+ } else {
+ Content::Fit
+ };
+
rect()
.horizontal()
.width(self.width)
.height(self.height)
.spacing(self.spacing)
.cross_align(Alignment::Center)
- .content(Content::Fit)
+ .content(content)
.children(self.tabs.into_iter().map(|tab| {
+ let min_width = overflows.then(|| min_tab_width(&tab.label, font_size));
TabButton {
label: tab.label,
active: tab.active,
count: tab.count,
font_size,
+ min_width,
on_press: tab.on_press,
}
.into_element()
@@ -166,6 +181,7 @@ struct TabButton {
active: bool,
count: Option,
font_size: f32,
+ min_width: Option,
on_press: Option>>,
}
@@ -181,6 +197,8 @@ impl Component for TabButton {
let mut el = rect()
.vertical()
.content(Content::Fit)
+ .cross_align(Alignment::Center)
+ .map(self.min_width, |el, w| el.min_width(Size::px(w)))
.a11y_id(a11y_id)
.a11y_focusable(true)
.a11y_role(AccessibilityRole::Button)
diff --git a/packages/oneclient_app/src/hooks/view_state.rs b/packages/oneclient_app/src/hooks/view_state.rs
index 7379d707..64696a34 100644
--- a/packages/oneclient_app/src/hooks/view_state.rs
+++ b/packages/oneclient_app/src/hooks/view_state.rs
@@ -7,7 +7,6 @@ use super::{use_dispatch, use_settings_snapshot};
pub struct PersistedView {
pub layout: State,
pub sort: State