From b4f067483a273713b576e3bc7d3f8d9c1322dbcf Mon Sep 17 00:00:00 2001 From: Maciek <108873945+rozwader@users.noreply.github.com> Date: Wed, 16 Sep 2026 18:58:10 +0200 Subject: [PATCH] fix: fixed most of the sizing issues in the launcher (#822) --- .../oneclient_app/assets/icons/icon-logo.svg | 4 + .../assets/icons/line-chart-up-01.svg | 3 + .../src/components/grid_columns.rs | 27 --- .../oneclient_app/src/components/icons.rs | 2 + packages/oneclient_app/src/components/mod.rs | 2 - .../src/components/navbar/app_navbar.rs | 87 ++++++-- .../oneclient_app/src/components/tab_bar.rs | 20 +- .../oneclient_app/src/hooks/view_state.rs | 10 +- .../src/layout/settings_shell.rs | 45 +++- packages/oneclient_app/src/ui.rs | 9 + .../src/view/app/analytics/charts.rs | 18 +- .../src/view/app/browser/index/cards.rs | 15 +- .../src/view/app/browser/index/mod.rs | 54 +---- .../src/view/app/browser/index/skeletons.rs | 14 +- .../src/view/app/browser/package/gallery.rs | 32 +-- .../view/app/cluster/package_manager/mod.rs | 5 +- .../view/app/cluster/package_manager/views.rs | 196 ++++++++++++------ .../src/view/app/cluster/screenshots.rs | 31 +-- .../src/view/app/clusters/page.rs | 68 ++++-- .../oneclient_core/src/settings/launcher.rs | 15 +- packages/oneclient_core/src/settings/mod.rs | 2 +- 21 files changed, 374 insertions(+), 285 deletions(-) create mode 100644 packages/oneclient_app/assets/icons/icon-logo.svg create mode 100644 packages/oneclient_app/assets/icons/line-chart-up-01.svg delete mode 100644 packages/oneclient_app/src/components/grid_columns.rs 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>, - pub columns: State, } pub fn use_view_state(key: &str) -> PersistedView { @@ -18,17 +17,14 @@ pub fn use_view_state(key: &str) -> PersistedView { let initial = settings.view_state(&key); let init_layout = initial.layout; let init_sort = initial.sort; - let init_columns = initial.columns; let layout = use_state(move || init_layout); let sort = use_state(move || init_sort.clone()); - let columns = use_state(move || init_columns); let mut first = use_state(|| true); use_side_effect(move || { let next = ViewState { layout: *layout.read(), sort: sort.read().clone(), - columns: *columns.read(), }; if *first.peek() { first.set(false); @@ -42,9 +38,5 @@ pub fn use_view_state(key: &str) -> PersistedView { dispatch.set_settings(updated); }); - PersistedView { - layout, - sort, - columns, - } + PersistedView { layout, sort } } diff --git a/packages/oneclient_app/src/layout/settings_shell.rs b/packages/oneclient_app/src/layout/settings_shell.rs index 3eaef38d..31b2ed33 100644 --- a/packages/oneclient_app/src/layout/settings_shell.rs +++ b/packages/oneclient_app/src/layout/settings_shell.rs @@ -20,6 +20,7 @@ use crate::use_dispatch; const SIDEBAR_WIDTH_PX: f32 = 225.; const ITEM_HEIGHT_PX: f32 = 33.; const SEARCH_WIDTH_PX: f32 = 256.; +const SEARCH_MIN_WIDTH_PX: f32 = 120.; const SEARCH_RESULTS_MAX: usize = 12; thread_local! { @@ -386,6 +387,7 @@ impl Component for SettingsShell { let changelog_unread = has_unread_changelog(); let search = use_state(String::new); + let header_width = use_state(|| 0f32); let query = SearchQuery::new(&search.read()); let scroll = use_scroll_controller(ScrollConfig::default); SETTINGS_SCROLL_CONTROLLER.with(|cell| *cell.borrow_mut() = Some(scroll)); @@ -433,7 +435,11 @@ impl Component for SettingsShell { .height(Size::fill()) .overflow(Overflow::Clip) .spacing(24.) - .child(content_header(active.label().to_string(), search)) + .child(content_header( + active.label().to_string(), + search, + header_width, + )) .child( ScrollArea::new() .width(Size::fill()) @@ -451,27 +457,49 @@ impl Component for SettingsShell { } } -fn content_header(title: String, search: State) -> impl IntoElement { +fn content_header( + title: String, + search: State, + mut width: State, +) -> impl IntoElement { + let search_width = search_width_for(*width.read()); + rect() .horizontal() .width(Size::fill()) + .content(Content::Flex) .cross_align(Alignment::Center) - .main_align(Alignment::SpaceBetween) + .spacing(12.) + .on_sized(move |event: Event| { + let next = event.data().area.width(); + if (*width.peek() - next).abs() > 0.5 { + width.set(next); + } + }) .child( label() .text(title) .font_size(30.) .font_weight(FontWeight::SEMI_BOLD) + .max_lines(1) + .width(Size::flex(1.0)) .color(colors::fg_primary()), ) - .child(search_box(search)) + .child(search_box(search, search_width)) } -fn search_box(mut search: State) -> impl IntoElement { +fn search_width_for(header_width: f32) -> f32 { + if header_width <= 0. { + return SEARCH_WIDTH_PX; + } + (header_width * 0.45).clamp(SEARCH_MIN_WIDTH_PX, SEARCH_WIDTH_PX) +} + +fn search_box(mut search: State, width: f32) -> impl IntoElement { let has_query = !search.read().trim().is_empty(); let base = TextInput::new(search) .placeholder("Search settings...") - .width(Size::px(SEARCH_WIDTH_PX)) + .width(Size::px(width)) .leading( Icon::new(IconType::SearchMd) .size(16.) @@ -632,7 +660,7 @@ fn sidebar(active: SettingsTab, changelog_unread: bool) -> impl IntoElement { .padding(Gaps::new(16., 0., 0., 0.)) .content(Content::Flex) .child( - rect() + ScrollArea::new() .width(Size::fill()) .height(Size::flex(1.0)) .spacing(16.) @@ -742,6 +770,9 @@ impl Component for SidebarInfo { .width(Size::fill()) .spacing(4.) .font_size(12.) + .padding(Gaps::new( + 15.,0.,0.,0. + )) .color(colors::fg_secondary()) .children( items diff --git a/packages/oneclient_app/src/ui.rs b/packages/oneclient_app/src/ui.rs index 8715856e..84eb9ba5 100644 --- a/packages/oneclient_app/src/ui.rs +++ b/packages/oneclient_app/src/ui.rs @@ -137,11 +137,20 @@ pub fn centered_note(text: &str) -> Element { .into_element() } +pub fn grid_columns_for_width(width: f32, max_col: f32, gap: f32) -> usize { + if width <= 0. { + return 1; + } + + (((width + gap) / (max_col + gap)).ceil() as usize).max(1) +} + pub fn columns_for(width: f32, min_cell: f32, max: usize, gap: f32) -> usize { let max = max.max(1); if width <= 0. { return max; } + (((width + gap) / (min_cell + gap)).floor() as usize).clamp(1, max) } diff --git a/packages/oneclient_app/src/view/app/analytics/charts.rs b/packages/oneclient_app/src/view/app/analytics/charts.rs index e349fae6..8acaae64 100644 --- a/packages/oneclient_app/src/view/app/analytics/charts.rs +++ b/packages/oneclient_app/src/view/app/analytics/charts.rs @@ -241,15 +241,15 @@ impl Component for DailyChart { .child(DateRangePicker::new(custom, (first_day, last_day)).height(30.)) .into_element(); - chart_card( - "Daily playtime", - subtitle, - Some(nav), - BarChart::new(values, labels) - .unit(ValueUnit::Duration) - .gap(3.) - .into_element(), - ) + chart_card( + "Daily playtime", + subtitle, + Some(nav), + BarChart::new(values, labels) + .unit(ValueUnit::Duration) + .gap(3.) + .into_element(), + ) } } diff --git a/packages/oneclient_app/src/view/app/browser/index/cards.rs b/packages/oneclient_app/src/view/app/browser/index/cards.rs index f75869c6..47641eb0 100644 --- a/packages/oneclient_app/src/view/app/browser/index/cards.rs +++ b/packages/oneclient_app/src/view/app/browser/index/cards.rs @@ -31,10 +31,10 @@ pub(super) fn grid_row( cluster_id: i64, package_type: &str, installed: &InstalledMap, - metrics: GridMetrics, + cols: usize, ) -> impl IntoElement { let package_type = package_type.to_string(); - let fill = metrics.cols.saturating_sub(row.len()); + let fill = cols.saturating_sub(row.len()); let installed: Vec> = row .iter() .map(|item| installed_for(installed, item)) @@ -43,14 +43,14 @@ pub(super) fn grid_row( rect() .horizontal() .width(Size::fill()) - .height(Size::px(metrics.card_h)) + .height(Size::px(CARD_H)) .spacing(GRID_SPACING) .content(Content::Flex) .children( row.into_iter() .zip(installed) .map(move |(item, installed)| { - PackageCard::new(item, cluster_id, package_type.clone(), installed, metrics) + PackageCard::new(item, cluster_id, package_type.clone(), installed) .into_element() }), ) @@ -77,7 +77,6 @@ struct PackageCard { cluster_id: i64, package_type: String, installed: Option, - metrics: GridMetrics, } impl PackageCard { @@ -86,14 +85,12 @@ impl PackageCard { cluster_id: i64, package_type: String, installed: Option, - metrics: GridMetrics, ) -> Self { Self { item, cluster_id, package_type, installed, - metrics, } } } @@ -113,7 +110,7 @@ impl Component for PackageCard { rect() .vertical() .width(Size::flex(1.0)) - .height(Size::px(self.metrics.card_h)) + .height(Size::px(CARD_H)) .corner_radius(CornerRadius::new_all(10.)) .background(CARD_BG) .border(border_all_color( @@ -135,7 +132,7 @@ impl Component for PackageCard { .margin(Gaps::new_all(1.)) .corner_radius(CornerRadius::new(10., 10., 0., 0.)) .overflow(Overflow::Clip) - .child(PackageBanner::new(icon_url, self.metrics.banner_h)) + .child(PackageBanner::new(icon_url, BANNER_H)) // Top-left of the banner the first thing worth knowing about a result and the opposite corner is the install button's .maybe_child(self.installed.map(|installed| { rect() diff --git a/packages/oneclient_app/src/view/app/browser/index/mod.rs b/packages/oneclient_app/src/view/app/browser/index/mod.rs index f7d91111..07ecb13f 100644 --- a/packages/oneclient_app/src/view/app/browser/index/mod.rs +++ b/packages/oneclient_app/src/view/app/browser/index/mod.rs @@ -16,7 +16,6 @@ use oneclient_core::settings::ViewLayout; use crate::components::{ Dropdown, Icon, IconType, Pagination, ScrollArea, Segment, SegmentedControl, TextInput, - cell_width, grid_columns_picker, resolved_columns, }; use crate::hooks::use_cluster; use crate::hooks::{ @@ -28,6 +27,7 @@ use crate::hooks::{ }; use crate::routes::Route; use crate::theme::colors; +use crate::ui::grid_columns_for_width; use super::{ InstallSource, Installed, PackageBanner, Thumbnail, installed_badge, installed_badge_overlay, @@ -52,21 +52,12 @@ const SEARCH_W: f32 = 260.; const SEARCH_COMPACT_W: f32 = 170.; const CARD_H: f32 = 240.; const BANNER_H: f32 = 100.; -const CARD_TEXT_H: f32 = CARD_H - BANNER_H; -const BANNER_RATIO: f32 = 0.35; -const BANNER_MAX_H: f32 = 200.; +const MAX_CARD_W: f32 = 400.; const LIST_ROW_H: f32 = 78.; const GRID_SPACING: f32 = 12.; const LIST_SPACING: f32 = 8.; const SEARCH_DEBOUNCE_MS: u64 = 250; -#[derive(Clone, Copy, PartialEq)] -pub(super) struct GridMetrics { - pub cols: usize, - pub card_h: f32, - pub banner_h: f32, -} - const BROWSE_TYPES: [(&str, &str); 3] = [ ("mod", "Mods"), ("texture", "Textures"), @@ -135,9 +126,7 @@ impl Component for BrowserBody { let query = use_state(|| saved.query.clone()); let provider = use_state(|| saved.provider); - let view = use_view_state(&format!("browser.{package_type}")); - let view_mode = view.layout; - let grid_columns = view.columns; + let view_mode = use_view_state(&format!("browser.{package_type}")).layout; let compatible_only = use_browser_compat(); let selected_categories = use_state(|| saved.categories.clone()); let page = use_state(|| saved.page); @@ -233,14 +222,11 @@ impl Component for BrowserBody { let mut grid_width = use_state(|| 0f32); let controls_width = use_state(|| 0f32); - let cols = resolved_columns(*grid_columns.read()); - let card_w = cell_width( + let cols = grid_columns_for_width( (*grid_width.read() - SCROLLBAR_GUTTER).max(0.), - cols, + MAX_CARD_W, GRID_SPACING, ); - let banner_h = (card_w * BANNER_RATIO).clamp(BANNER_H, BANNER_MAX_H); - let card_h = banner_h + CARD_TEXT_H; let fade_dep = (current_page, packages.is_empty(), pending, mode); let fade = use_animation_with_dependencies(&fade_dep, |conf, _| { @@ -264,19 +250,8 @@ impl Component for BrowserBody { let rows: Vec> = packages.chunks(cols).map(|c| c.to_vec()).collect(); - sa.lazy(rows.len(), card_h, GRID_SPACING, move |i| { - grid_row( - rows[i].clone(), - cluster_id, - &pkg, - &installed, - GridMetrics { - cols, - card_h, - banner_h, - }, - ) - .into_element() + sa.lazy(rows.len(), CARD_H, GRID_SPACING, move |i| { + grid_row(rows[i].clone(), cluster_id, &pkg, &installed, cols).into_element() }) } ViewLayout::List => sa.lazy(packages.len(), LIST_ROW_H, LIST_SPACING, move |i| { @@ -287,13 +262,8 @@ impl Component for BrowserBody { match mode { ViewLayout::Grid => { let rows = BROWSE_PAGE_SIZE.div_ceil(cols); - sa.lazy(rows, card_h, GRID_SPACING, move |_| { - skeleton_grid_row(GridMetrics { - cols, - card_h, - banner_h, - }) - .into_element() + sa.lazy(rows, CARD_H, GRID_SPACING, move |_| { + skeleton_grid_row(cols).into_element() }) } ViewLayout::List => sa.lazy(BROWSE_PAGE_SIZE, LIST_ROW_H, LIST_SPACING, |_| { @@ -329,7 +299,6 @@ impl Component for BrowserBody { provider, query, view_mode, - grid_columns, controls_width, )) .child( @@ -510,7 +479,6 @@ fn controls( provider: State, query: State, view_mode: State, - grid_columns: State, mut controls_width: State, ) -> impl IntoElement { let measured = *controls_width.read(); @@ -535,10 +503,6 @@ fn controls( }) .child(type_picker) .child(rect().width(Size::flex(1.0))) - .maybe_child( - (*view_mode.read() == ViewLayout::Grid) - .then(|| grid_columns_picker(grid_columns, 30.).into_element()), - ) .child( SegmentedControl::new(view_mode) .equal_width(30.) diff --git a/packages/oneclient_app/src/view/app/browser/index/skeletons.rs b/packages/oneclient_app/src/view/app/browser/index/skeletons.rs index ee6dca57..77f8ae4f 100644 --- a/packages/oneclient_app/src/view/app/browser/index/skeletons.rs +++ b/packages/oneclient_app/src/view/app/browser/index/skeletons.rs @@ -27,9 +27,7 @@ fn use_skeleton_pulse() -> f32 { } #[derive(PartialEq)] -struct SkeletonCard { - metrics: GridMetrics, -} +struct SkeletonCard; impl Component for SkeletonCard { fn render(&self) -> impl IntoElement { @@ -37,13 +35,13 @@ impl Component for SkeletonCard { rect() .vertical() .width(Size::flex(1.0)) - .height(Size::px(self.metrics.card_h)) + .height(Size::px(CARD_H)) .corner_radius(CornerRadius::new_all(10.)) .background(CARD_BG) .border(border_all_color(1., colors::component_border())) .overflow(Overflow::Clip) .opacity(pulse) - .child(skeleton_block(Size::fill(), self.metrics.banner_h)) + .child(skeleton_block(Size::fill(), BANNER_H)) .child( rect() .vertical() @@ -90,13 +88,13 @@ impl Component for SkeletonListRow { } } -pub(super) fn skeleton_grid_row(metrics: GridMetrics) -> impl IntoElement { +pub(super) fn skeleton_grid_row(cols: usize) -> impl IntoElement { rect() .horizontal() .width(Size::fill()) - .height(Size::px(metrics.card_h)) + .height(Size::px(CARD_H)) .spacing(GRID_SPACING) .content(Content::Flex) - .children((0..metrics.cols).map(move |_| SkeletonCard { metrics }.into_element())) + .children((0..cols).map(|_| SkeletonCard.into_element())) .into_element() } diff --git a/packages/oneclient_app/src/view/app/browser/package/gallery.rs b/packages/oneclient_app/src/view/app/browser/package/gallery.rs index 74736f58..898fdfc1 100644 --- a/packages/oneclient_app/src/view/app/browser/package/gallery.rs +++ b/packages/oneclient_app/src/view/app/browser/package/gallery.rs @@ -4,18 +4,14 @@ use super::*; use oneclient_content::packages::types::GalleryImage; -use crate::components::{ - Button, Icon, IconType, OverlayPopup, cell_width, grid_columns_picker, resolved_columns, -}; -use crate::hooks::{loaded_image, query_is_busy, use_cached_image, use_view_state}; +use crate::components::{Button, Icon, IconType, OverlayPopup}; +use crate::hooks::{loaded_image, query_is_busy, use_cached_image}; use crate::theme::colors; -use crate::ui::{ImageFallbackExt, border_all_color, flow_grid}; +use crate::ui::{ImageFallbackExt, border_all_color, flow_grid, grid_columns_for_width}; const MAX_COL_W: f32 = 400.; const GRID_GAP: f32 = 16.; const TILE_PREVIEW_H: f32 = 168.; -const TILE_PREVIEW_RATIO: f32 = TILE_PREVIEW_H / MAX_COL_W; -const TILE_PREVIEW_MAX_H: f32 = 420.; /// Downscale ceilings for the image cache full-size tiles made the grid slow to fill const TILE_EDGE: u32 = 640; @@ -35,11 +31,6 @@ impl Component for Gallery { let grid_width = use_state(|| 0f32); let mut viewing = use_state(|| None::); - let grid_columns = use_view_state("browser.gallery").columns; - let cols = resolved_columns(*grid_columns.read()); - let tile_w = cell_width(*grid_width.read(), cols, GRID_GAP); - let preview_h = (tile_w * TILE_PREVIEW_RATIO).clamp(TILE_PREVIEW_H, TILE_PREVIEW_MAX_H); - let tiles: Vec = self .images .iter() @@ -48,7 +39,6 @@ impl Component for Gallery { let key = image.url.clone(); GalleryTile { image: image.clone(), - preview_h, on_open: (move |()| viewing.set(Some(idx))).into(), key: DiffKey::None, } @@ -57,21 +47,11 @@ impl Component for Gallery { }) .collect(); + let cols = grid_columns_for_width(*grid_width.read(), MAX_COL_W, GRID_GAP); let images = self.images.clone(); rect() - .vertical() .width(Size::fill()) - .spacing(10.) - .child( - rect() - .horizontal() - .width(Size::fill()) - .cross_align(Alignment::Center) - .content(Content::Flex) - .child(rect().width(Size::flex(1.0))) - .child(grid_columns_picker(grid_columns, 30.)), - ) .child(flow_grid(tiles, cols, grid_width, GRID_GAP)) .maybe_child((*viewing.read()).map(|start| { GalleryViewer { @@ -87,7 +67,6 @@ impl Component for Gallery { #[derive(PartialEq)] struct GalleryTile { image: GalleryImage, - preview_h: f32, on_open: EventHandler<()>, key: DiffKey, } @@ -108,7 +87,7 @@ impl Component for GalleryTile { let preview = rect() .width(Size::fill()) - .height(Size::px(self.preview_h)) + .height(Size::px(TILE_PREVIEW_H)) .center() .overflow(Overflow::Clip) .background(colors::component_bg()) @@ -125,6 +104,7 @@ impl Component for GalleryTile { rect() .vertical() .width(Size::flex(1.0)) + .max_width(Size::px(MAX_COL_W)) .corner_radius(CornerRadius::new_all(10.)) .overflow(Overflow::Clip) .background(PANEL_BG) diff --git a/packages/oneclient_app/src/view/app/cluster/package_manager/mod.rs b/packages/oneclient_app/src/view/app/cluster/package_manager/mod.rs index 441657b0..6119a953 100644 --- a/packages/oneclient_app/src/view/app/cluster/package_manager/mod.rs +++ b/packages/oneclient_app/src/view/app/cluster/package_manager/mod.rs @@ -419,10 +419,10 @@ impl Component for PackageManager { let search = use_state(String::new); let enabled_filter = use_state(|| EnabledFilter::All); let hidden_filter = use_state(|| HiddenFilter::Hide); + let toolbar_width = use_state(|| 0f32); let view = use_view_state("cluster.packages"); let sort = view.sort; let layout = view.layout; - let grid_columns = view.columns; let query = SearchQuery::new(&search.read()); let sort_mode = sort .read() @@ -475,9 +475,9 @@ impl Component for PackageManager { enabled_filter, hidden_filter, layout, - grid_columns, cluster_id, package_type, + toolbar_width, )) .maybe_child( content_type @@ -493,7 +493,6 @@ impl Component for PackageManager { cluster_id, content_kind, card_layout, - *grid_columns.read(), )) } } diff --git a/packages/oneclient_app/src/view/app/cluster/package_manager/views.rs b/packages/oneclient_app/src/view/app/cluster/package_manager/views.rs index 23cc80e6..d028cd41 100644 --- a/packages/oneclient_app/src/view/app/cluster/package_manager/views.rs +++ b/packages/oneclient_app/src/view/app/cluster/package_manager/views.rs @@ -7,7 +7,7 @@ use oneclient_core::settings::ViewLayout; use crate::components::{ Button, CardLayout, Icon, IconType, PackageEntry, PackageRow, ScrollArea, ScrollAreaCtx, - Segment, SegmentedControl, TabBar, TabItem, TextInput, grid_columns_picker, resolved_columns, + Segment, SegmentedControl, TabBar, TabItem, TextInput, }; use crate::hooks::{use_dispatch, use_overlay_claim}; use crate::routes::Route; @@ -122,6 +122,17 @@ impl HiddenFilter { const FILTER_PANEL_W: f32 = 172.; const FILTER_BTN_W: f32 = 34.; +const TOOLBAR_STACK_W: f32 = 640.; +const TABS_ROW_H: f32 = 34.; + +fn tabs_scrollbar_theme() -> ScrollBarThemePartial { + ScrollBarThemePartial { + thumb_background: Some(colors::fg_secondary().with_a(120).into()), + hover_thumb_background: Some(colors::fg_secondary().with_a(190).into()), + active_thumb_background: Some(colors::fg_primary().into()), + ..Default::default() + } +} #[allow(clippy::too_many_arguments)] pub(super) fn toolbar_bar( @@ -134,9 +145,9 @@ pub(super) fn toolbar_bar( enabled_filter: State, hidden_filter: State, layout: State, - grid_columns: State, cluster_id: i64, package_type: &'static str, + mut toolbar_width: State, ) -> impl IntoElement { let tab_items = tabs.iter().enumerate().map(|(i, tab)| { let mut active = active; @@ -146,69 +157,127 @@ pub(super) fn toolbar_bar( let mut top_corners = CornerRadius::new_all(0.); top_corners.fill_top(12.); - rect() - .horizontal() - .width(Size::fill()) - .cross_align(Alignment::Center) - .spacing(8.) - .content(Content::Flex) - .overflow(Overflow::Clip) - .padding(Gaps::new_symmetric(8., 12.)) - .corner_radius(top_corners) - .background(colors::page_elevated()) + let measured = *toolbar_width.read(); + let stacked = measured > 0. && measured < TOOLBAR_STACK_W; + + let filter_tabs = ScrollView::new() + .direction(Direction::Horizontal) + .invert_scroll_wheel(true) + .show_scrollbar(stacked) + .scrollbar_theme(tabs_scrollbar_theme()) + .width(if stacked { + Size::fill() + } else { + Size::flex(1.0) + }) + .height(if stacked { + Size::fill() + } else { + Size::auto() + }) .child( - ScrollView::new() - .direction(Direction::Horizontal) - .show_scrollbar(false) - .width(Size::flex(1.0)) - .height(Size::auto()) - .child( - TabBar::new() - .width(Size::auto()) - .height(Size::auto()) - .spacing(20.) - .font_size(12.) - .tabs(tab_items), - ), + TabBar::new() + .width(Size::auto()) + .height(if stacked { + Size::fill() + } else { + Size::auto() + }) + .spacing(20.) + .font_size(12.) + .tabs(tab_items), ) - .child( - TextInput::new(search) - .placeholder("Search...") - .width(Size::px(180.)) - .leading( - Icon::new(IconType::SearchMd) - .size(14.) - .color(colors::fg_secondary()) - .into_element(), - ), - ) - .child(FilterButton { + .into_element(); + + let mut controls: Vec = vec![ + TextInput::new(search) + .placeholder("Search...") + .width(Size::px(180.)) + .leading( + Icon::new(IconType::SearchMd) + .size(14.) + .color(colors::fg_secondary()) + .into_element(), + ) + .into_element(), + FilterButton { sort, current_sort, enabled_filter, hidden_filter, + } + .into_element(), + ]; + + controls.push( + SegmentedControl::new(layout) + .height(34.) + .icon_size(15.) + .equal_width(34.) + .segment(Segment::new(ViewLayout::List).icon(IconType::ParagraphWrap)) + .segment(Segment::new(ViewLayout::Grid).icon(IconType::DotsGrid)) + .into_element(), + ); + controls.push( + Button::new() + .primary() + .height(Size::px(34.)) + .font_size(12.) + .on_press(move |_| open_browser(cluster_id, package_type)) + .child(Icon::new(IconType::Plus).size(15.)) + .text("Add Content") + .into_element(), + ); + + let inner = if stacked { + rect() + .vertical() + .width(Size::fill()) + .spacing(8.) + .child( + rect() + .horizontal() + .width(Size::fill()) + .height(Size::px(TABS_ROW_H)) + .cross_align(Alignment::Center) + .child(filter_tabs), + ) + .child( + rect() + .horizontal() + .width(Size::fill()) + .cross_align(Alignment::Center) + .spacing(8.) + .content(Content::Flex) + .children(controls), + ) + .into_element() + } else { + rect() + .horizontal() + .width(Size::fill()) + .cross_align(Alignment::Center) + .spacing(8.) + .content(Content::Flex) + .child(filter_tabs) + .children(controls) + .into_element() + }; + + rect() + .vertical() + .width(Size::fill()) + .overflow(Overflow::Clip) + .padding(Gaps::new_symmetric(8., 12.)) + .corner_radius(top_corners) + .background(colors::page_elevated()) + .on_sized(move |event: Event| { + let next = event.data().area.width(); + if (*toolbar_width.peek() - next).abs() > 0.5 { + toolbar_width.set(next); + } }) - .maybe_child( - (*layout.read() == ViewLayout::Grid) - .then(|| grid_columns_picker(grid_columns, 34.).into_element()), - ) - .child( - SegmentedControl::new(layout) - .height(34.) - .icon_size(15.) - .equal_width(34.) - .segment(Segment::new(ViewLayout::List).icon(IconType::ParagraphWrap)) - .segment(Segment::new(ViewLayout::Grid).icon(IconType::DotsGrid)), - ) - .child( - Button::new() - .primary() - .height(Size::px(34.)) - .font_size(12.) - .on_press(move |_| open_browser(cluster_id, package_type)) - .child(Icon::new(IconType::Plus).size(15.)) - .text("Add Content"), - ) + .child(inner) .into_element() } @@ -576,7 +645,6 @@ pub(super) struct ContentBox { cluster_id: i64, kind: ContentKind, layout: CardLayout, - grid_columns: u8, } impl ContentBox { @@ -589,7 +657,6 @@ impl ContentBox { cluster_id: i64, kind: ContentKind, layout: CardLayout, - grid_columns: u8, ) -> Self { Self { items, @@ -599,7 +666,6 @@ impl ContentBox { cluster_id, kind, layout, - grid_columns, } } } @@ -613,7 +679,6 @@ impl Component for ContentBox { let noun_plural = self.noun_plural; let kind = &self.kind; let layout = self.layout; - let grid_columns = self.grid_columns; let dispatch = use_dispatch(); @@ -640,7 +705,7 @@ impl Component for ContentBox { .height(Size::fill()) .scrollbar_gutter(true) .content(move |ctx: ScrollAreaCtx| { - grid_content(&items, package_type, cluster_id, grid_columns, ctx).into_element() + grid_content(&items, package_type, cluster_id, ctx).into_element() }) .into_element(), }); @@ -705,12 +770,11 @@ fn grid_content( items: &[PackageEntry], package_type: &'static str, cluster_id: i64, - grid_columns: u8, ctx: ScrollAreaCtx, ) -> impl IntoElement { let count = items.len(); - let fits = ((ctx.viewport_w + GRID_GAP) / (GRID_MIN_W + GRID_GAP)).floor() as usize; - let cols = resolved_columns(grid_columns).min(fits.max(1)); + let cols = + (((ctx.viewport_w + GRID_GAP) / (GRID_MIN_W + GRID_GAP)).floor() as usize).clamp(1, 5); let rows_total = count.div_ceil(cols); let slot = CARD_GRID_H + GRID_GAP; diff --git a/packages/oneclient_app/src/view/app/cluster/screenshots.rs b/packages/oneclient_app/src/view/app/cluster/screenshots.rs index bc3e24c9..a7a8905a 100644 --- a/packages/oneclient_app/src/view/app/cluster/screenshots.rs +++ b/packages/oneclient_app/src/view/app/cluster/screenshots.rs @@ -7,13 +7,12 @@ use oneclient_core::settings::ViewLayout; use crate::components::{ Button, Icon, IconType, LocalImage, OverlayPopup, ScreenshotViewer, ScrollArea, Segment, - SegmentedControl, cell_width, grid_columns_picker, open_folder_button, resolved_columns, - screenshot_context_menu, + SegmentedControl, open_folder_button, screenshot_context_menu, }; use crate::hooks::{ScreenshotAction, query_is_loading, try_cluster_screenshots, use_cluster_screenshots, use_dispatch, use_screenshot_action, use_screenshot_folder_watch, use_view_state}; use crate::layout::cluster_content; use crate::theme::colors; -use crate::ui::{border_all_color, flow_grid, fmt_date}; +use crate::ui::{border_all_color, flow_grid, fmt_date, grid_columns_for_width}; use crate::utils::{format_res, format_size}; use super::cluster_not_found; @@ -23,8 +22,6 @@ const THUMB_EDGE: u32 = 480; const MAX_COL_W: f32 = 400.; const GRID_GAP: f32 = 16.; const TILE_PREVIEW_H: f32 = 168.; -const TILE_PREVIEW_RATIO: f32 = TILE_PREVIEW_H / MAX_COL_W; -const TILE_PREVIEW_MAX_H: f32 = 420.; const CARD_BG: Color = Color::from_rgb(26, 34, 41); const CARD_NAME: Color = Color::from_rgb(213, 219, 255); @@ -48,9 +45,7 @@ impl Component for ClusterScreenshots { let shots = try_cluster_screenshots(&query).unwrap_or_default(); - let view = use_view_state("cluster.screenshots"); - let view_mode = view.layout; - let grid_columns = view.columns; + let view_mode = use_view_state("cluster.screenshots").layout; let menu_dispatch = use_dispatch(); let mut edit_mode = use_state(|| false); @@ -64,16 +59,11 @@ impl Component for ClusterScreenshots { &shots, folder, view_mode, - grid_columns, edit_mode, selected, confirm_delete, ); - let cols = resolved_columns(*grid_columns.read()); - let tile_w = cell_width(*grid_width.read(), cols, GRID_GAP); - let preview_h = (tile_w * TILE_PREVIEW_RATIO).clamp(TILE_PREVIEW_H, TILE_PREVIEW_MAX_H); - let content: Element = if shots.is_empty() { empty_state(query_is_loading(&query)) .into_element() @@ -108,7 +98,6 @@ impl Component for ClusterScreenshots { info, selected: is_selected, edit_mode: editing, - preview_h, on_activate: on_activate.into(), on_context, } @@ -126,7 +115,10 @@ impl Component for ClusterScreenshots { } match *view_mode.read() { - ViewLayout::Grid => flow_grid(items, cols, grid_width, GRID_GAP), + ViewLayout::Grid => { + let cols = grid_columns_for_width(*grid_width.read(), MAX_COL_W, GRID_GAP); + flow_grid(items, cols, grid_width, GRID_GAP) + } ViewLayout::List => rect() .vertical() .width(Size::fill()) @@ -185,7 +177,6 @@ fn toolbar_row( shots: &[ScreenshotInfo], folder: Option, view_mode: State, - grid_columns: State, mut edit_mode: State, mut selected: State>, mut confirm_delete: State, @@ -253,10 +244,6 @@ fn toolbar_row( ); } else { right = right - .maybe_child( - (*view_mode.read() == ViewLayout::Grid) - .then(|| grid_columns_picker(grid_columns, 36.).into_element()), - ) .child( SegmentedControl::new(view_mode) .equal_width(40.) @@ -406,7 +393,6 @@ struct ScreenshotTile { info: ScreenshotInfo, selected: bool, edit_mode: bool, - preview_h: f32, on_activate: EventHandler<()>, on_context: EventHandler<(f32, f32)>, } @@ -431,6 +417,7 @@ impl Component for ScreenshotTile { rect() .vertical() .width(Size::flex(1.0)) + .max_width(Size::px(MAX_COL_W)) .corner_radius(CornerRadius::new_all(10.)) .background(CARD_BG) .overflow(Overflow::Clip) @@ -446,7 +433,7 @@ impl Component for ScreenshotTile { }) .child(preview_box( info.path.clone(), - self.preview_h, + TILE_PREVIEW_H, info.resolution, )) .child( diff --git a/packages/oneclient_app/src/view/app/clusters/page.rs b/packages/oneclient_app/src/view/app/clusters/page.rs index 14b74fa0..f1306920 100644 --- a/packages/oneclient_app/src/view/app/clusters/page.rs +++ b/packages/oneclient_app/src/view/app/clusters/page.rs @@ -22,6 +22,8 @@ const MIN_CARD_WIDTH_PX: f32 = 280.; const SIDEBAR_WIDTH_PX: f32 = 300.; const CARD_HEIGHT_PX: f32 = 146.; const PLACEHOLDER_VERSION_INFO: &str = "Placeholder version info"; +const ART_MAX_HEIGHT_PX: f32 = 140.; +const ART_MIN_SIDEBAR_PX: f32 = 260.; #[derive(PartialEq)] pub struct Clusters; @@ -196,6 +198,7 @@ struct DetailSidebar { impl Component for DetailSidebar { fn render(&self) -> impl IntoElement { + let mut sidebar_height = use_state(|| 0f32); let active_id = use_active_cluster_id(); let dispatch = use_dispatch(); let game = use_game_snapshot(); @@ -224,6 +227,8 @@ impl Component for DetailSidebar { .map(|m| m.tags.clone()) .unwrap_or_default(); + let art_height = art_height_for(*sidebar_height.read()); + rect() .width(Size::px(SIDEBAR_WIDTH_PX)) .min_width(Size::px(SIDEBAR_WIDTH_PX)) @@ -231,13 +236,29 @@ impl Component for DetailSidebar { .vertical() .spacing(8.) .padding(8.) + .content(Content::Flex) .corner_radius(CornerRadius::new_all(12.)) .background(colors::page_elevated()) .border(border_all_color(1., colors::component_border())) .overflow(Overflow::Clip) - .child(rect().width(Size::fill()).max_height(Size::px(140.)).child( - ClusterLandscapeArt::for_version(self.line.major, version_value, loader_value, false), - )) + .on_sized(move |event: Event| { + let height = event.data().area.height(); + if (*sidebar_height.peek() - height).abs() > 0.5 { + *sidebar_height.write() = height; + } + }) + .maybe_child(art_height.map(|max| { + rect() + .width(Size::fill()) + .max_height(Size::px(max)) + .child(ClusterLandscapeArt::for_version( + self.line.major, + version_value, + loader_value, + false, + )) + .into_element() + })) .child( rect() .vertical() @@ -251,20 +272,27 @@ impl Component for DetailSidebar { .vertical() .width(Size::fill()) .height(Size::flex(1.0)) + .content(Content::Flex) .spacing(4.) .child( - label() - .text(heading) - .font_size(24.) - .font_weight(FontWeight::SEMI_BOLD) - .color(colors::fg_primary()), - ) - .maybe_child(tags_row(&tags)) - .child( - label() - .text(description) - .font_size(12.) - .color(colors::fg_secondary()), + ScrollArea::new() + .width(Size::fill()) + .height(Size::flex(1.0)) + .spacing(4.) + .child( + label() + .text(heading) + .font_size(24.) + .font_weight(FontWeight::SEMI_BOLD) + .color(colors::fg_primary()), + ) + .children(tags_row(&tags)) + .child( + label() + .text(description) + .font_size(12.) + .color(colors::fg_secondary()), + ), ) .children(version_rows(self.line, &keys, version_value, self.selected_version)) .children(loader_rows(&loaders, loader_value, self.selected_loader)), @@ -288,6 +316,16 @@ impl Component for DetailSidebar { } } +fn art_height_for(sidebar_height: f32) -> Option { + if sidebar_height <= 0. { + return Some(ART_MAX_HEIGHT_PX); + } + if sidebar_height < ART_MIN_SIDEBAR_PX { + return None; + } + Some((sidebar_height * 0.32).min(ART_MAX_HEIGHT_PX)) +} + fn tags_row(tags: &[String]) -> Option { if tags.is_empty() { return None; diff --git a/packages/oneclient_core/src/settings/launcher.rs b/packages/oneclient_core/src/settings/launcher.rs index 57a2c61f..81db7eac 100644 --- a/packages/oneclient_core/src/settings/launcher.rs +++ b/packages/oneclient_core/src/settings/launcher.rs @@ -13,25 +13,12 @@ pub enum ViewLayout { List, } -pub const DEFAULT_GRID_COLUMNS: u8 = 3; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)] #[serde(default)] pub struct ViewState { pub layout: ViewLayout, #[serde(skip_serializing_if = "Option::is_none")] pub sort: Option, - pub columns: u8, -} - -impl Default for ViewState { - fn default() -> Self { - Self { - layout: ViewLayout::default(), - sort: None, - columns: DEFAULT_GRID_COLUMNS, - } - } } #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/packages/oneclient_core/src/settings/mod.rs b/packages/oneclient_core/src/settings/mod.rs index 56bdd5a2..2c54d3af 100644 --- a/packages/oneclient_core/src/settings/mod.rs +++ b/packages/oneclient_core/src/settings/mod.rs @@ -2,7 +2,7 @@ mod launcher; pub mod data_dir; -pub use launcher::{DEFAULT_GRID_COLUMNS, LauncherSettings, ViewLayout, ViewState}; +pub use launcher::{LauncherSettings, ViewLayout, ViewState}; pub use oneclient_cluster::{ GameSettingsProfile, PackageUpdateMode, ProfileUpdate, SettingsOsExtra, };