From 4db0e87b6691542698623459d0067d58651c2a95 Mon Sep 17 00:00:00 2001 From: Roman Makarov Date: Sun, 13 Sep 2026 13:04:47 +0200 Subject: [PATCH] feat: oc cards layout (#1178) * feat: change OC layout to cards * feat: switch from checkbox to toggle-buttons for OC optional features * put into size-group, some small fixes * make p-states more compact * remove core offset p-states label * add dedigated features block for sections * hacky fix for padding * update p-state section toggle * fix adv block bottom border * use info-dialog to guard manual p-lvl for p-states conf * upd p-level description to fit in split layout * independent all p-state toggle for core/vram * p-level/powermizer: use more compact description * rollback description changes due to better fix * use OcAdjustment for performance limit * shorten description a bit * remove nvidia oc description * rename power_section to power_frame for consistency * fix signal blocking and simplify power frame visibility * feat: move warning to sidebar and make it more specific * phrasing * remove wrapper * remove OC-adjustment group, make adjustment components shared. * rename css for consistency * make adjustment row control it's state * remove oc_adjustment_row wrapper * simplify power_frames adjustment wiring * fix css priority to avoid overrides * breeze theme should be between adwaita and app css * rename secondary_clocks * set border radius to features buttons * implement MasonryLayout via LayoutManager * remember children's column * Revert "remember children's column" This reverts commit ed82e94effb7e8a9b5f2bdfe2425f3163e84eca3. * use 2 columns and flowbox instead of masonry * cleanup * redo layout with single column order control * hide reset button on vram frame * fix specificity * upd spacing * shorten labels * chore: better label, disable all pstates button when it's not usable --------- Co-authored-by: Ilya Zlobintsev --- lact-gui/i18n/en/lact_gui.ftl | 49 +- lact-gui/src/app.css | 9 - lact-gui/src/app.rs | 10 +- .../src/app/components/adjustment_card.css | 15 + .../src/app/components/adjustment_card.rs | 44 ++ lact-gui/src/app/components/mod.rs | 1 + lact-gui/src/app/pages/oc_page.css | 4 + lact-gui/src/app/pages/oc_page.rs | 133 +++-- lact-gui/src/app/pages/oc_page/card_layout.rs | 127 ++++ .../src/app/pages/oc_page/clocks_frame.css | 11 - .../oc_page/clocks_frame/adjustment_group.rs | 231 -------- .../src/app/pages/oc_page/clocks_frame/mod.rs | 542 ++++++++++-------- .../pages/oc_page/performance_frame/mod.rs | 24 +- .../app/pages/oc_page/power_cap_section.rs | 141 ----- lact-gui/src/app/pages/oc_page/power_frame.rs | 177 ++++++ .../power_states/power_states_frame.rs | 89 +-- lact-gui/src/app/pages/oc_page/vf_curve.rs | 4 +- lact-gui/src/app/utils/styles.rs | 5 +- 18 files changed, 828 insertions(+), 788 deletions(-) create mode 100644 lact-gui/src/app/components/adjustment_card.css create mode 100644 lact-gui/src/app/components/adjustment_card.rs create mode 100644 lact-gui/src/app/pages/oc_page.css create mode 100644 lact-gui/src/app/pages/oc_page/card_layout.rs delete mode 100644 lact-gui/src/app/pages/oc_page/clocks_frame.css delete mode 100644 lact-gui/src/app/pages/oc_page/clocks_frame/adjustment_group.rs delete mode 100644 lact-gui/src/app/pages/oc_page/power_cap_section.rs create mode 100644 lact-gui/src/app/pages/oc_page/power_frame.rs diff --git a/lact-gui/i18n/en/lact_gui.ftl b/lact-gui/i18n/en/lact_gui.ftl index 1ff09f6f..b7eef67c 100644 --- a/lact-gui/i18n/en/lact_gui.ftl +++ b/lact-gui/i18n/en/lact_gui.ftl @@ -112,7 +112,8 @@ confirm-settings = Confirm Settings revert-button = Revert settings-confirmation = Do you want to keep the new settings? (Reverting in {$seconds_left} seconds) -power-cap = Power Usage Limit +power-cap = Power Limit +power-section = Power watt = W ghz = GHz @@ -124,12 +125,14 @@ gibibyte = GiB stats-section = Statistics gpu-clock = GPU Core Clock +core-section = Core gpu-clock-avg = GPU Core Clock (Average) gpu-clock-target = GPU Core Clock (Target) gpu-voltage = GPU Voltage gpu-temp = Temperature gpu-usage = GPU Usage vram-clock = VRAM Clock +vram-section = VRAM power-usage = Power Usage extra-clocks = Extra Clocks no-throttling = No @@ -169,21 +172,10 @@ power-mizer-mode-prefer-maximum-performance-description = Favor maximum performa power-mizer-mode-prefer-consistent-performance-description = Lock to GPU base clocks. overclock-section = Clockspeed and Voltage -nvidia-oc-info = Overclocking Information -nvidia-oc-description = - Overclocking functionality on Nvidia includes setting offsets for GPU/VRAM clockspeeds and limiting the potential range of clockspeeds using the "locked clocks" feature. - - On many cards, the VRAM clockpeed offset will only affect the actual memory clockspeed by half of the offset value. - For example, a +1000MHz VRAM offset may only increase the measured VRAM speed by 500MHz. - This is normal, and is how Nvidia handles GDDR data rates. Adjust your overclock accordingly. - - It is possible to achieve a pseudo-undervolt by combining the locked clocks option with a positive clockspeed offset. - This will force the GPU to run at a voltage that's constrained by the locked clocks, while achieving a higher clockspeed due to the offset. - This can cause system instability if pushed too high. -show-all-pstates = Show all P-States -enable-gpu-locked-clocks = Enable GPU Locked Clocks -enable-vram-locked-clocks = Enable VRAM Locked Clocks -enable-vf-curve = Enable VF Curve Edit +advanced-features = Advanced features +show-all-pstates = All P-States +enable-locked-clocks = Locked Clocks +enable-vf-curve = Custom VF Curve pstate-list-description = The following values are clock offsets for each P-State, going from highest to lowest. no-clocks-data = No clocks data available reset-oc-tooltip = Warning: this resets all clock settings to defaults! @@ -200,22 +192,17 @@ vf-curve-flatten-right = Flatten curve to the right vf-curve-flatten-selection = Flatten selection vf-curve-editing-disabled = VF curve editing is disabled on the OC page -gpu-clock-offset = GPU Clock Offset (MHz) -max-gpu-clock = Maximum GPU Clock (MHz) -max-vram-clock = Maximum VRAM Clock (MHz) -max-gpu-voltage = Maximum GPU Voltage (mV) -min-gpu-clock = Minimum GPU Clock (MHz) -min-vram-clock = Minimum VRAM Clock (MHz) -min-gpu-voltage = Minimum GPU Voltage (mV) -gpu-voltage-offset = GPU voltage offset (mV) -gpu-voltage-boost = GPU Voltage Boost (%) +gpu-clock-offset = Clock Offset (MHz) +max-clock = Maximum Clock (MHz) +max-gpu-voltage = Maximum Voltage (mV) +min-clock = Minimum Clock (MHz) +min-gpu-voltage = Minimum Voltage (mV) +gpu-voltage-offset = Voltage Offset (mV) +gpu-voltage-boost = Voltage Boost (%) gpu-voltage-boost-tooltip = Controls how much of the additional voltage headroom defined by the driver is available. 100% means all of this headroom, not 100% of total GPU voltage. More headroom may sustain higher clockspeeds but increases power draw and heat. -gpu-pstate-clock-offset = GPU P-State {$pstate} Clock Offset (MHz) -vram-pstate-clock-offset = VRAM P-State {$pstate} Clock Offset (MHz) -gpu-pstate-clock = GPU P-State {$pstate} Clock (MHz) -mem-pstate-clock = VRAM P-State {$pstate} Clock (MHz) -gpu-pstate-clock-voltage = GPU P-State {$pstate} Voltage (mV) -mem-pstate-clock-voltage = VRAM P-State {$pstate} Voltage (mV) +pstate-clock-offset = P-State {$pstate} Clock Offset (MHz) +pstate-clock = P-State {$pstate} Clock (MHz) +pstate-clock-voltage = P-State {$pstate} Voltage (mV) pstates = Power States gpu-pstates = GPU Power States diff --git a/lact-gui/src/app.css b/lact-gui/src/app.css index 9f6f8b10..8abc2fcc 100644 --- a/lact-gui/src/app.css +++ b/lact-gui/src/app.css @@ -13,12 +13,3 @@ .main-sidebar-container .sidebar-section { margin: 4px 8px 8px 8px; } - -.oc-warning-button > button { - color: @warning_color; - background: alpha(@warning_color, 0.15); -} - -.oc-warning-button > button:hover { - background: alpha(@warning_color, 0.25); -} diff --git a/lact-gui/src/app.rs b/lact-gui/src/app.rs index 7a63c152..6f962fd4 100644 --- a/lact-gui/src/app.rs +++ b/lact-gui/src/app.rs @@ -39,10 +39,7 @@ use crate::{ use adw::prelude::*; use anyhow::{Context, anyhow}; use graphs_window::{GraphsWindow, GraphsWindowMsg}; -use gtk::{ - STYLE_PROVIDER_PRIORITY_APPLICATION, - glib::{self, ControlFlow, clone}, -}; +use gtk::glib::{self, ControlFlow, clone}; use i18n_embed_fl::fl; use lact_client::{ConnectionStatusMsg, DaemonClient}; use lact_schema::{ @@ -368,10 +365,7 @@ impl AsyncComponent for AppModel { .application() .expect("Failed to get application from root window"); - relm4::set_global_css_with_priority( - styles::COMBINED_CSS, - STYLE_PROVIDER_PRIORITY_APPLICATION, - ); + relm4::set_global_css_with_priority(styles::COMBINED_CSS, styles::APPLICATION_CSS_PRIORITY); if let Err(err) = styles::apply_theme(CONFIG.read().theme) { error!("could not apply theme: {err:#}"); diff --git a/lact-gui/src/app/components/adjustment_card.css b/lact-gui/src/app/components/adjustment_card.css new file mode 100644 index 00000000..3128d13b --- /dev/null +++ b/lact-gui/src/app/components/adjustment_card.css @@ -0,0 +1,15 @@ +.adjustment-card-advanced-features { + background-color: alpha(@window_fg_color, 0.05); + border-radius: 9px 9px 0 0; + margin: -10px -10px 0; /* nulify parent's padding */ + padding: 10px; +} + +.adjustment-card-advanced-features .adjustment-card-option-toggle { + border-radius: 25px; +} + +.adjustment-card-advanced-features .adjustment-card-option-toggle:checked { + background-color: @accent_bg_color; + color: @accent_fg_color; +} diff --git a/lact-gui/src/app/components/adjustment_card.rs b/lact-gui/src/app/components/adjustment_card.rs new file mode 100644 index 00000000..c2f3be41 --- /dev/null +++ b/lact-gui/src/app/components/adjustment_card.rs @@ -0,0 +1,44 @@ +use crate::I18N; +use gtk::prelude::*; +use i18n_embed_fl::fl; +use relm4::css; + +#[relm4::widget_template(pub)] +impl relm4::WidgetTemplate for AdjustmentCard { + view! { + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 10, + set_hexpand: true, + set_valign: gtk::Align::Start, + + #[name = "advanced_features"] + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 5, + set_visible: false, + add_css_class: "adjustment-card-advanced-features", + + gtk::Label { + set_label: &fl!(I18N, "advanced-features"), + set_halign: gtk::Align::Start, + add_css_class: css::DIM_LABEL, + add_css_class: css::CAPTION, + }, + + #[name = "controls"] + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_spacing: 10, + set_halign: gtk::Align::Start, + }, + }, + + #[name = "content"] + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 10, + }, + } + } +} diff --git a/lact-gui/src/app/components/mod.rs b/lact-gui/src/app/components/mod.rs index c86a9675..8057c4a3 100644 --- a/lact-gui/src/app/components/mod.rs +++ b/lact-gui/src/app/components/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod adjustment_card; pub(crate) mod adjustment_row; pub(crate) mod adjustment_value; pub(crate) mod gpu_stats_section; diff --git a/lact-gui/src/app/pages/oc_page.css b/lact-gui/src/app/pages/oc_page.css new file mode 100644 index 00000000..9050ea4c --- /dev/null +++ b/lact-gui/src/app/pages/oc_page.css @@ -0,0 +1,4 @@ +.oc-page-section { + min-width: 400px; + padding: 0; +} diff --git a/lact-gui/src/app/pages/oc_page.rs b/lact-gui/src/app/pages/oc_page.rs index fa54b8e4..6e6f750b 100644 --- a/lact-gui/src/app/pages/oc_page.rs +++ b/lact-gui/src/app/pages/oc_page.rs @@ -1,6 +1,7 @@ +mod card_layout; mod clocks_frame; mod performance_frame; -mod power_cap_section; +mod power_frame; mod power_states; mod vf_curve; @@ -11,17 +12,18 @@ use crate::app::components::gpu_stats_section::{ use crate::app::pages::PageUpdate; use crate::app::utils::ext::RelmLaunchable as _; use crate::app::{msg::AppMsg, utils::ext::RelmDefaultLauchable}; +use adw::prelude::*; use amdgpu_sysfs::gpu_handle::{ PerformanceLevel, PowerLevelKind, power_profile_mode::PowerProfileModesTable, }; -use clocks_frame::{ClocksFrame, ClocksFrameInit, ClocksFrameMsg}; -use gtk::prelude::{BoxExt, OrientableExt, WidgetExt}; +use card_layout::{CardLayout, ColumnBias}; +use clocks_frame::{ClockDomain, ClocksFrame, ClocksFrameInit, ClocksFrameMsg}; use indexmap::IndexMap; use lact_schema::config; use lact_schema::{ClocksTable, DeviceInfo, PowerStates}; use nvml_wrapper::enums::device::PowerMizerMode; -use performance_frame::{PerformanceFrame, PerformanceFrameMsg}; -use power_cap_section::{PowerCapMsg, PowerCapSection}; +use performance_frame::PerformanceFrameMsg; +use power_frame::{PowerFrame, PowerFrameMsg}; use power_states::power_states_frame::{PowerStatesFrame, PowerStatesFrameMsg}; use relm4::binding::BoolBinding; use relm4::{ComponentController, ComponentParts, ComponentSender, RelmWidgetExt}; @@ -34,10 +36,10 @@ pub struct OcPage { stats_section: relm4::Controller, device_info: Option>, - performance_frame: relm4::Controller, - power_cap_section: relm4::Controller, + power_frame: relm4::Controller, power_states_frame: relm4::Controller, - clocks_frame: relm4::Controller, + gpu_clocks_frame: relm4::Controller, + vram_clocks_frame: relm4::Controller, vf_curve_editor: relm4::Controller, } @@ -74,15 +76,38 @@ impl relm4::Component for OcPage { view! { gtk::Box { set_orientation: gtk::Orientation::Vertical, - set_spacing: 10, + set_spacing: 15, set_margin_all: 15, set_margin_top: 20, // align with gpu picker model.stats_section.widget(), - model.power_cap_section.widget(), - model.performance_frame.widget(), - model.power_states_frame.widget(), - model.clocks_frame.widget(), + + gtk::Box { + set_layout_manager: Some(CardLayout::new([ + ColumnBias::Left, // Core + ColumnBias::Right, // VRAM + ColumnBias::Left, // Power + ColumnBias::Right, // Power States + + ])), + set_valign: gtk::Align::Start, + + model.gpu_clocks_frame.widget() { + add_css_class: "oc-page-section", + }, + + model.vram_clocks_frame.widget() { + add_css_class: "oc-page-section", + }, + + model.power_frame.widget() { + add_css_class: "oc-page-section", + }, + + model.power_states_frame.widget() { + add_css_class: "oc-page-section", + }, + }, }, } @@ -107,16 +132,22 @@ impl relm4::Component for OcPage { GpuStat::ExtraClocks, ]), }); - let power_cap_section = PowerCapSection::detach_default(); let vf_curve_editing = BoolBinding::new(false); - let clocks_frame = ClocksFrame::launch(ClocksFrameInit { + let gpu_clocks_frame = ClocksFrame::launch(ClocksFrameInit { + domain: ClockDomain::Gpu, vf_curve_editing: vf_curve_editing.clone(), + show_all_pstates: BoolBinding::new(false), + }) + .forward(sender.input_sender(), |msg| msg); + let vram_clocks_frame = ClocksFrame::launch(ClocksFrameInit { + domain: ClockDomain::Vram, + vf_curve_editing: BoolBinding::new(false), + show_all_pstates: BoolBinding::new(false), }) .forward(sender.input_sender(), |msg| msg); let power_states_frame = PowerStatesFrame::launch_default().forward(sender.input_sender(), |msg| msg); - let performance_frame = - PerformanceFrame::launch_default().forward(sender.input_sender(), |msg| msg); + let power_frame = PowerFrame::launch_default().forward(sender.input_sender(), |msg| msg); let vf_curve_editor = VfCurveEditor::detach(VfCurveEditorInit { global_settings_changed: settings_changed, @@ -126,10 +157,10 @@ impl relm4::Component for OcPage { let model = Self { stats_section, device_info: None, - performance_frame, - power_cap_section, + power_frame, power_states_frame, - clocks_frame, + gpu_clocks_frame, + vram_clocks_frame, vf_curve_editor, }; @@ -158,25 +189,17 @@ impl relm4::Component for OcPage { .emit(VfCurveEditorMsg::Stats(stats.clone())); if initial { - self.power_cap_section - .emit(PowerCapMsg::Update(update.clone())); - - if stats.power.cap_current.is_some() { - self.power_cap_section.widget().set_visible(true); - } else { - self.power_cap_section.widget().set_visible(false); - } - - self.performance_frame - .emit(PerformanceFrameMsg::PerformanceLevel( - stats.performance_level, - )); - self.performance_frame - .emit(PerformanceFrameMsg::PowerMizerInfo { + self.power_frame + .emit(PowerFrameMsg::PowerStats(stats.power.clone())); + self.power_frame.emit(PowerFrameMsg::Performance( + PerformanceFrameMsg::PerformanceLevel(stats.performance_level), + )); + self.power_frame.emit(PowerFrameMsg::Performance( + PerformanceFrameMsg::PowerMizerInfo { active: stats.active_power_mizer_mode, supported: stats.supported_power_mizer_modes.clone(), - }); - sender.input(OcPageMsg::PerformanceLevelChanged); + }, + )); } } PageUpdate::Info(info) => { @@ -187,7 +210,7 @@ impl relm4::Component for OcPage { .emit(GpuStatsSectionMsg::Info(info.clone())); self.power_states_frame .emit(PowerStatesFrameMsg::VramClockRatio(vram_clock_ratio)); - self.clocks_frame + self.vram_clocks_frame .emit(ClocksFrameMsg::VramRatio(vram_clock_ratio)); } }, @@ -197,7 +220,11 @@ impl relm4::Component for OcPage { } => { let table = table.map(Arc::new); - self.clocks_frame.emit(ClocksFrameMsg::Clocks { + self.gpu_clocks_frame.emit(ClocksFrameMsg::Clocks { + table: table.clone(), + vf_curve_is_configured, + }); + self.vram_clocks_frame.emit(ClocksFrameMsg::Clocks { table: table.clone(), vf_curve_is_configured, }); @@ -205,8 +232,9 @@ impl relm4::Component for OcPage { .emit(VfCurveEditorMsg::Clocks(table.clone())); } OcPageMsg::ProfileModesTable(modes_table) => { - self.performance_frame - .emit(PerformanceFrameMsg::PowerProfileModes(modes_table)); + self.power_frame.emit(PowerFrameMsg::Performance( + PerformanceFrameMsg::PowerProfileModes(modes_table), + )); } OcPageMsg::PowerStates { pstates, @@ -235,8 +263,9 @@ impl relm4::Component for OcPage { )); } OcPageMsg::SetPerformanceLevel(level) => { - self.performance_frame - .emit(PerformanceFrameMsg::PerformanceLevel(Some(level))); + self.power_frame.emit(PowerFrameMsg::Performance( + PerformanceFrameMsg::PerformanceLevel(Some(level)), + )); APP_BROKER.send(AppMsg::SettingsChanged); } OcPageMsg::EnablePstateConfig => { @@ -248,7 +277,8 @@ impl relm4::Component for OcPage { } OcPageMsg::VfCurveEditingToggled(enabled) => { if enabled { - self.clocks_frame.emit(ClocksFrameMsg::ResetGpuClockOffsets); + self.gpu_clocks_frame + .emit(ClocksFrameMsg::ResetGpuClockOffsets); } else { self.vf_curve_editor.emit(VfCurveEditorMsg::ResetCurve); } @@ -261,29 +291,30 @@ impl relm4::Component for OcPage { impl OcPage { pub fn get_performance_level(&self) -> Option { - self.performance_frame.model().performance_level() + self.power_frame.model().performance_level() } pub fn get_active_power_mizer_mode(&self) -> Option { - self.performance_frame.model().active_power_mizer_mode() + self.power_frame.model().active_power_mizer_mode() } pub fn get_power_profile_mode(&self) -> Option { - self.performance_frame.model().power_profile_mode() + self.power_frame.model().power_profile_mode() } pub fn get_power_profile_mode_custom_heuristics(&self) -> Vec>> { - self.performance_frame + self.power_frame .model() .power_profile_mode_custom_heuristics() } pub fn get_power_cap(&self) -> Option { - self.power_cap_section.model().get_user_cap() + self.power_frame.model().get_user_cap() } pub fn apply_clocks_config(&self, config: &mut config::ClocksConfiguration) { - let commands = self.clocks_frame.model().get_commands(); + let mut commands = self.gpu_clocks_frame.model().get_commands(); + commands.extend(self.vram_clocks_frame.model().get_commands()); debug!("applying clocks commands {commands:#?}"); @@ -297,7 +328,7 @@ impl OcPage { } pub fn get_enabled_power_states(&self) -> IndexMap> { - if self.performance_frame.model().performance_level() == Some(PerformanceLevel::Manual) { + if self.get_performance_level() == Some(PerformanceLevel::Manual) { self.power_states_frame.model().get_enabled_power_states() } else { IndexMap::new() diff --git a/lact-gui/src/app/pages/oc_page/card_layout.rs b/lact-gui/src/app/pages/oc_page/card_layout.rs new file mode 100644 index 00000000..88844140 --- /dev/null +++ b/lact-gui/src/app/pages/oc_page/card_layout.rs @@ -0,0 +1,127 @@ +use gtk::{glib, prelude::*, subclass::prelude::*}; +use std::cell::OnceCell; + +#[derive(Clone, Copy, Debug)] +pub enum ColumnBias { + Left, + Right, +} + +glib::wrapper! { + pub struct CardLayout(ObjectSubclass) + @extends gtk::LayoutManager; +} + +impl CardLayout { + pub fn new(column_bias: impl Into>) -> Self { + let layout: Self = glib::Object::new(); + layout.imp().column_bias.set(column_bias.into()).unwrap(); + layout + } +} + +mod imp { + use super::*; + + #[derive(Default)] + pub struct CardLayout { + pub column_bias: OnceCell>, + } + + #[glib::object_subclass] + impl ObjectSubclass for CardLayout { + const NAME: &'static str = "CardLayout"; + type Type = super::CardLayout; + type ParentType = gtk::LayoutManager; + } + + impl ObjectImpl for CardLayout {} + + impl LayoutManagerImpl for CardLayout { + fn request_mode(&self, _widget: >k::Widget) -> gtk::SizeRequestMode { + gtk::SizeRequestMode::HeightForWidth + } + + fn measure( + &self, + widget: >k::Widget, + orientation: gtk::Orientation, + for_size: i32, + ) -> (i32, i32, i32, i32) { + let (minimum, natural) = measure_width(widget); + if orientation == gtk::Orientation::Horizontal { + (minimum, natural, -1, -1) + } else { + let width = if for_size < 0 { natural } else { for_size }; + let height = layout(widget, width, self.column_bias.get().unwrap(), |_, _| {}); + (height, height, -1, -1) + } + } + + fn allocate(&self, widget: >k::Widget, width: i32, _height: i32, _baseline: i32) { + layout( + widget, + width, + self.column_bias.get().unwrap(), + |child, allocation| { + child.size_allocate(allocation, -1); + }, + ); + } + } +} + +const SPACING: i32 = 15; + +fn children(widget: >k::Widget) -> impl Iterator { + std::iter::successors(widget.first_child(), |child| child.next_sibling()) + .enumerate() + .filter(|(_, child)| child.should_layout()) +} + +fn measure_width(widget: >k::Widget) -> (i32, i32) { + let mut minimum = 0; + let mut natural = 0; + let mut count = 0; + for (_, child) in children(widget) { + let (child_minimum, child_natural, _, _) = child.measure(gtk::Orientation::Horizontal, -1); + minimum = minimum.max(child_minimum); + natural = natural.max(child_natural); + count += 1; + } + if count > 1 { + natural = natural * 2 + SPACING; + } + (minimum, natural) +} + +// Measurement and allocation share placement so the scroller gets the full height. +fn layout( + widget: >k::Widget, + width: i32, + column_bias: &[ColumnBias], + mut place: impl FnMut(>k::Widget, >k::Allocation), +) -> i32 { + let (minimum, _) = measure_width(widget); + let columns = if children(widget).count() > 1 && width >= minimum * 2 + SPACING { + 2 + } else { + 1 + }; + let column_width = ((width - SPACING * (columns as i32 - 1)) / columns as i32).max(0); + let mut heights = [0, 0]; + for (index, child) in children(widget) { + let column = usize::from(columns == 2 && matches!(column_bias[index], ColumnBias::Right)); + let x = column as i32 * (column_width + SPACING); + let child_width = if column == columns - 1 { + width - x + } else { + column_width + }; + let (_, height, _, _) = child.measure(gtk::Orientation::Vertical, child_width); + let y = heights[column]; + place(&child, >k::Allocation::new(x, y, child_width, height)); + heights[column] = y + height + SPACING; + } + (heights[0].max(heights[1]) - SPACING).max(0) +} diff --git a/lact-gui/src/app/pages/oc_page/clocks_frame.css b/lact-gui/src/app/pages/oc_page/clocks_frame.css deleted file mode 100644 index 07e6eb0f..00000000 --- a/lact-gui/src/app/pages/oc_page/clocks_frame.css +++ /dev/null @@ -1,11 +0,0 @@ -.clocks-frame-group { - padding: 0; -} - -.clocks-frame-group .card { - padding: 10px; -} - -.clocks-frame-group .adjustment-row > scale { - min-width: 100px; -} diff --git a/lact-gui/src/app/pages/oc_page/clocks_frame/adjustment_group.rs b/lact-gui/src/app/pages/oc_page/clocks_frame/adjustment_group.rs deleted file mode 100644 index 01595493..00000000 --- a/lact-gui/src/app/pages/oc_page/clocks_frame/adjustment_group.rs +++ /dev/null @@ -1,231 +0,0 @@ -use super::{ClocksData, clock_title}; -use crate::{ - APP_BROKER, I18N, - app::{ - components::adjustment_row::{AdjustmentRow, AdjustmentRowInit, AdjustmentRowMsg}, - msg::AppMsg, - }, -}; -use adw::prelude::*; -use i18n_embed_fl::fl; -use lact_schema::request::ClockspeedType; -use relm4::{css, factory::FactoryHashMap, prelude::FactoryComponent}; -use std::collections::HashSet; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum ClockCategory { - CoreClock, - CoreVoltage, - VramClock, - CoreCurveClock, - VramCurveClock, - CoreCurveVoltage, - VramCurveVoltage, -} - -impl ClockCategory { - pub fn from_type(clock_type: ClockspeedType) -> Self { - match clock_type { - ClockspeedType::MaxCoreClock - | ClockspeedType::MinCoreClock - | ClockspeedType::GpuClockOffset(_) => ClockCategory::CoreClock, - ClockspeedType::MinVoltage - | ClockspeedType::MaxVoltage - | ClockspeedType::VoltageOffset - | ClockspeedType::VoltageBoost => ClockCategory::CoreVoltage, - ClockspeedType::MaxMemoryClock - | ClockspeedType::MinMemoryClock - | ClockspeedType::MemClockOffset(_) => ClockCategory::VramClock, - ClockspeedType::GpuVfCurveClock(_) => ClockCategory::CoreCurveClock, - ClockspeedType::MemVfCurveClock(_) => ClockCategory::VramCurveClock, - ClockspeedType::GpuVfCurveVoltage(_) => ClockCategory::CoreCurveVoltage, - ClockspeedType::MemVfCurveVoltage(_) => ClockCategory::VramCurveVoltage, - ClockspeedType::Reset => unreachable!(), - } - } - - pub fn is_core(&self) -> bool { - Self::CORE.contains(self) - } - - pub fn is_vram(&self) -> bool { - Self::VRAM.contains(self) - } - - pub const CORE: [ClockCategory; 4] = [ - ClockCategory::CoreClock, - ClockCategory::CoreVoltage, - ClockCategory::CoreCurveClock, - ClockCategory::CoreCurveVoltage, - ]; - - pub const VRAM: [ClockCategory; 3] = [ - ClockCategory::VramClock, - ClockCategory::VramCurveClock, - ClockCategory::VramCurveVoltage, - ]; -} - -pub struct AdjustmentGroup { - adjustments: FactoryHashMap>, - secondary_clocks: HashSet, -} - -impl AdjustmentGroup { - pub fn is_empty(&self) -> bool { - self.adjustments.is_empty() - } - - pub fn has_secondary(&self) -> bool { - !self.secondary_clocks.is_empty() - } - - pub fn set_clock(&mut self, clock_type: ClockspeedType, data: ClocksData) { - self.adjustments.insert( - clock_type, - AdjustmentRowInit { - title: data.custom_title.unwrap_or_else(|| clock_title(clock_type)), - info_text: if clock_type == ClockspeedType::VoltageBoost { - fl!(I18N, "gpu-voltage-boost-tooltip") - } else { - String::new() - }, - value: f64::from(data.current), - lower: f64::from(data.min), - upper: f64::from(data.max), - step_increment: get_row_step(clock_type), - ..Default::default() - }, - ); - if data.is_secondary { - self.secondary_clocks.insert(clock_type); - } else { - self.secondary_clocks.remove(&clock_type); - } - } - - pub fn add_size_group(&self, label_group: gtk::SizeGroup, input_group: gtk::SizeGroup) { - for clock_type in self.adjustments.keys() { - self.adjustments.send( - clock_type, - AdjustmentRowMsg::AddSizeGroup { - label_group: label_group.clone(), - input_group: input_group.clone(), - }, - ); - } - } - - pub fn set_value_ratio(&self, ratio: f64) { - for clock_type in self.adjustments.keys() { - self.adjustments - .send(clock_type, AdjustmentRowMsg::ValueRatio(ratio)); - } - } - - pub fn toggle_secondary_visibility( - &self, - show_secondary: bool, - show_nvidia_options: bool, - enable_gpu_locked: bool, - enable_vram_locked: bool, - vf_curve_editing: bool, - ) { - let mut any_visible = false; - - for key in self.adjustments.keys() { - let show_current = match key { - ClockspeedType::MaxCoreClock | ClockspeedType::MinCoreClock - if show_nvidia_options => - { - enable_gpu_locked - } - ClockspeedType::MaxMemoryClock | ClockspeedType::MinMemoryClock - if show_nvidia_options => - { - enable_vram_locked - } - ClockspeedType::GpuClockOffset(_) if show_nvidia_options && vf_curve_editing => { - false - } - _ => !self.secondary_clocks.contains(key) || show_secondary, - }; - - any_visible |= show_current; - - self.adjustments - .send(key, AdjustmentRowMsg::SetVisible(show_current)); - } - - // removes empty card - self.adjustments.widget().set_visible(any_visible); - } - - pub fn get_commands(&self) -> Vec<(ClockspeedType, Option)> { - self.adjustments - .iter() - .map(|(clock_type, row)| { - ( - *clock_type, - row.get_changed_value().map(|value| value as i32), - ) - }) - .collect() - } - - pub fn reset_gpu_clock_offsets(&self) { - for clock_type in self.adjustments.keys() { - if matches!(clock_type, ClockspeedType::GpuClockOffset(_)) { - self.adjustments - .send(clock_type, AdjustmentRowMsg::SetValue(0.0)); - } - } - } - - pub fn get_raw_value(&self, clock_type: ClockspeedType) -> i32 { - self.adjustments - .get(&clock_type) - .map(|row| row.get_value() as i32) - .unwrap_or(0) - } -} - -fn get_row_step(clock_type: ClockspeedType) -> f64 { - match ClockCategory::from_type(clock_type) { - ClockCategory::CoreClock - | ClockCategory::VramClock - | ClockCategory::CoreCurveClock - | ClockCategory::VramCurveClock => 5.0, - ClockCategory::CoreVoltage - | ClockCategory::CoreCurveVoltage - | ClockCategory::VramCurveVoltage => 1.0, - } -} - -#[relm4::factory(pub)] -impl FactoryComponent for AdjustmentGroup { - type Init = (); - type Input = (); - type Output = (); - type CommandOutput = (); - type ParentWidget = gtk::Box; - type Index = ClockCategory; - - view! { - self.adjustments.widget().clone() -> gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_spacing: 5, - set_valign: gtk::Align::Start, - add_css_class: css::CARD, - } - } - - fn init_model(_: Self::Init, _: &Self::Index, _: relm4::FactorySender) -> Self { - Self { - adjustments: FactoryHashMap::builder() - .launch_default() - .forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged), - secondary_clocks: HashSet::new(), - } - } -} diff --git a/lact-gui/src/app/pages/oc_page/clocks_frame/mod.rs b/lact-gui/src/app/pages/oc_page/clocks_frame/mod.rs index ed085525..e07a9857 100644 --- a/lact-gui/src/app/pages/oc_page/clocks_frame/mod.rs +++ b/lact-gui/src/app/pages/oc_page/clocks_frame/mod.rs @@ -1,16 +1,17 @@ -mod adjustment_group; - use crate::{ APP_BROKER, I18N, - app::{components::page_section::PageSection, msg::AppMsg, pages::oc_page::OcPageMsg}, + app::{ + components::{ + adjustment_card::AdjustmentCard, + adjustment_row::{AdjustmentRow, AdjustmentRowInit, AdjustmentRowMsg}, + page_section::PageSection, + }, + msg::AppMsg, + pages::oc_page::OcPageMsg, + }, }; -use adjustment_group::{AdjustmentGroup, ClockCategory}; +use adw::prelude::*; use amdgpu_sysfs::gpu_handle::overdrive::ClocksTableGen as AmdClocksTable; -use gtk::{ - glib::object::ObjectExt, - pango, - prelude::{BoxExt, ButtonExt, CheckButtonExt, OrientableExt, WidgetExt}, -}; use i18n_embed_fl::fl; use lact_schema::{ ClocksTable, IntelClocksTable, NvidiaClockOffset, NvidiaClocksTable, @@ -20,20 +21,20 @@ use relm4::{ ComponentParts, ComponentSender, RelmObjectExt, RelmWidgetExt, binding::BoolBinding, css, factory::FactoryHashMap, }; -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; const DEFAULT_VOLTAGE_OFFSET_RANGE: i32 = 250; pub struct ClocksFrame { - core_groups: FactoryHashMap, - vram_groups: FactoryHashMap, + adjustments: FactoryHashMap>, + secondary_p_state_clocks: HashSet, + domain: ClockDomain, vram_clock_ratio: f64, show_nvidia_options: bool, vf_curve_available: bool, show_all_pstates: BoolBinding, vf_curve_editing: BoolBinding, - enable_gpu_locked_clocks: BoolBinding, - enable_vram_locked_clocks: BoolBinding, + enable_locked_clocks: BoolBinding, } #[derive(Default)] @@ -42,7 +43,7 @@ struct ClocksData { min: i32, max: i32, custom_title: Option, - is_secondary: bool, + is_secondary_p_state: bool, } impl ClocksData { @@ -57,7 +58,38 @@ impl ClocksData { } pub struct ClocksFrameInit { + pub domain: ClockDomain, pub vf_curve_editing: BoolBinding, + pub show_all_pstates: BoolBinding, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ClockDomain { + Gpu, + Vram, +} + +impl ClockDomain { + fn matches(self, clock_type: ClockspeedType) -> bool { + let domain = match clock_type { + ClockspeedType::MaxCoreClock + | ClockspeedType::MinCoreClock + | ClockspeedType::GpuClockOffset(_) + | ClockspeedType::MinVoltage + | ClockspeedType::MaxVoltage + | ClockspeedType::VoltageOffset + | ClockspeedType::VoltageBoost + | ClockspeedType::GpuVfCurveClock(_) + | ClockspeedType::GpuVfCurveVoltage(_) => Self::Gpu, + ClockspeedType::MaxMemoryClock + | ClockspeedType::MinMemoryClock + | ClockspeedType::MemClockOffset(_) + | ClockspeedType::MemVfCurveClock(_) + | ClockspeedType::MemVfCurveVoltage(_) => Self::Vram, + ClockspeedType::Reset => unreachable!(), + }; + self == domain + } } #[derive(Debug)] @@ -79,36 +111,27 @@ impl relm4::Component for ClocksFrame { type CommandOutput = (); view! { - PageSection::new(&fl!(I18N, "overclock-section")) { - set_hide_visible_container: true, - add_css_class: "clocks-frame", + PageSection::new("") { + #[watch] + set_name: match model.domain { + ClockDomain::Gpu => fl!(I18N, "core-section"), + ClockDomain::Vram => fl!(I18N, "vram-section"), + }, + #[watch] + set_visible: model.domain == ClockDomain::Gpu || model.has_any_clocks(), append_header = >k::Box { set_spacing: 10, set_hexpand: true, set_halign: gtk::Align::End, - append = >k::MenuButton { - #[watch] - set_visible: model.show_nvidia_options, - set_label: &fl!(I18N, "nvidia-oc-info"), - - #[wrap(Some)] - set_popover = >k::Popover { - gtk::Label { - set_margin_all: 5, - set_markup: &fl!(I18N, "nvidia-oc-description"), - set_wrap: true, - set_max_width_chars: 75, - } - } - }, - append = >k::Button { set_label: &fl!(I18N, "vf-curve-editor"), #[watch] - set_visible: model.show_nvidia_options && model.vf_curve_available, + set_visible: model.domain == ClockDomain::Gpu + && model.show_nvidia_options + && model.vf_curve_available, connect_clicked[sender] => move |_| { sender.output(OcPageMsg::ShowVfCurveEditor).unwrap(); @@ -122,7 +145,7 @@ impl relm4::Component for ClocksFrame { add_css_class: css::DESTRUCTIVE_ACTION, #[watch] - set_visible: model.has_any_clocks(), + set_visible: model.domain == ClockDomain::Gpu && model.has_any_clocks(), connect_clicked => move |_| { APP_BROKER.send(AppMsg::ResetClocks); @@ -130,141 +153,129 @@ impl relm4::Component for ClocksFrame { }, }, - append_child = >k::Box { - set_orientation: gtk::Orientation::Vertical, - set_spacing: 5, + #[template] + append_child = &AdjustmentCard { + #[template_child] + advanced_features { + #[watch] + set_visible: model.has_secondary_p_states() || model.show_nvidia_options, + }, - append = >k::Box { - set_orientation: gtk::Orientation::Horizontal, - set_homogeneous: true, - set_hexpand: true, - - append = >k::CheckButton { + #[template_child] + controls { + append = >k::ToggleButton { #[watch] - set_visible: model.any_is_secondary(), + set_visible: model.has_secondary_p_states(), - set_label: Some(&fl!(I18N, "show-all-pstates")), + add_css_class: "adjustment-card-option-toggle", add_binding["active"]: &model.show_all_pstates, + + #[watch] + set_sensitive: { + if model.domain == ClockDomain::Gpu + && model.show_nvidia_options + && model.vf_curve_available { + !model.vf_curve_editing.value() + } else { + true + } + }, + + #[wrap(Some)] + set_child = >k::Box { + append = >k::Label { + set_label: &fl!(I18N, "show-all-pstates"), + }, + }, }, - append: gpu_locked_clocks_togglebutton = >k::CheckButton { + append: locked_clocks_togglebutton = >k::ToggleButton { #[watch] set_visible: model.show_nvidia_options, - set_label: Some(&fl!(I18N, "enable-gpu-locked-clocks")), - add_binding["active"]: &model.enable_gpu_locked_clocks, + add_css_class: "adjustment-card-option-toggle", + add_binding["active"]: &model.enable_locked_clocks, + + #[wrap(Some)] + set_child = >k::Box { + append = >k::Label { + set_label: &fl!(I18N, "enable-locked-clocks"), + }, + }, connect_toggled => move |_| { APP_BROKER.send(AppMsg::SettingsChanged); - } @ gpu_locked_clock_signal, + } @ locked_clock_signal, }, - append: vf_curve_editing_togglebutton = >k::CheckButton { + append: vf_curve_editing_togglebutton = >k::ToggleButton { #[watch] - set_visible: model.show_nvidia_options && model.vf_curve_available, - set_label: Some(&fl!(I18N, "enable-vf-curve")), + set_visible: model.domain == ClockDomain::Gpu + && model.show_nvidia_options + && model.vf_curve_available, + add_css_class: "adjustment-card-option-toggle", add_css_class: css::WARNING, add_binding["active"]: &model.vf_curve_editing, + + #[wrap(Some)] + set_child = >k::Box { + append = >k::Label { + set_label: &fl!(I18N, "enable-vf-curve"), + }, + }, + connect_toggled[sender] => move |button| { sender.output(OcPageMsg::VfCurveEditingToggled(button.is_active())).unwrap(); APP_BROKER.send(AppMsg::SettingsChanged); } @ vf_curve_editing_signal, }, + }, - append: vram_locked_clocks_togglebutton = >k::CheckButton { + #[template_child] + content { + #[local_ref] + adjustments_widget -> gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 5, + }, + + gtk::Label { + set_label: &fl!(I18N, "no-clocks-data"), + set_margin_horizontal: 10, + set_halign: gtk::Align::Start, #[watch] - set_visible: model.show_nvidia_options, - set_label: Some(&fl!(I18N, "enable-vram-locked-clocks")), - add_binding["active"]: &model.enable_vram_locked_clocks, - connect_toggled => move |_| { - APP_BROKER.send(AppMsg::SettingsChanged); - } @ vram_locked_clock_signal, + set_visible: !model.has_any_clocks(), }, }, - - append = >k::Label { - #[watch] - set_visible: model.show_all_pstates.value() && model.show_nvidia_options, - - set_margin_horizontal: 5, - set_markup: &fl!(I18N, "pstate-list-description"), - set_wrap_mode: pango::WrapMode::Word, - set_halign: gtk::Align::Start, - }, - }, - - append_child = >k::FlowBox { - set_orientation: gtk::Orientation::Horizontal, - set_selection_mode: gtk::SelectionMode::None, - #[watch] - set_max_children_per_line: if model.core_any_visible() && model.vram_any_visible() { 2 } else { 1 }, - set_column_spacing: 10, - set_row_spacing: 10, - set_homogeneous: false, - set_valign: gtk::Align::Start, - set_hexpand: true, - - append = >k::FlowBoxChild { - add_css_class: "clocks-frame-group", - set_valign: gtk::Align::Start, - #[watch] - set_visible: model.core_any_visible(), - - #[local_ref] - core_groups_widget -> gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_valign: gtk::Align::Start, - set_spacing: 10, - set_hexpand: true, - }, - }, - - append = >k::FlowBoxChild { - add_css_class: "clocks-frame-group", - set_valign: gtk::Align::Start, - #[watch] - set_visible: model.vram_any_visible(), - - #[local_ref] - vram_groups_widget -> gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_valign: gtk::Align::Start, - set_spacing: 10, - set_hexpand: true, - }, - }, - }, - - append_child = >k::Label { - set_label: &fl!(I18N, "no-clocks-data"), - set_margin_horizontal: 10, - set_halign: gtk::Align::Start, - #[watch] - set_visible: !model.has_any_clocks(), }, } } fn init( - ClocksFrameInit { vf_curve_editing }: Self::Init, + ClocksFrameInit { + domain, + vf_curve_editing, + show_all_pstates, + }: Self::Init, root: Self::Root, sender: ComponentSender, ) -> ComponentParts { let model = Self { - core_groups: FactoryHashMap::builder().launch_default().detach(), - vram_groups: FactoryHashMap::builder().launch_default().detach(), + adjustments: FactoryHashMap::builder() + .launch_default() + .forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged), + secondary_p_state_clocks: HashSet::new(), + domain, vram_clock_ratio: 1.0, show_nvidia_options: false, vf_curve_available: false, - show_all_pstates: BoolBinding::new(false), + show_all_pstates, vf_curve_editing, - enable_gpu_locked_clocks: BoolBinding::new(false), - enable_vram_locked_clocks: BoolBinding::new(false), + enable_locked_clocks: BoolBinding::new(false), }; for binding in [ &model.show_all_pstates, &model.vf_curve_editing, - &model.enable_gpu_locked_clocks, - &model.enable_vram_locked_clocks, + &model.enable_locked_clocks, ] { let sender = sender.clone(); binding.connect_value_notify(move |_| { @@ -272,9 +283,7 @@ impl relm4::Component for ClocksFrame { }); } - let core_groups_widget = model.core_groups.widget(); - let vram_groups_widget = model.vram_groups.widget(); - + let adjustments_widget = model.adjustments.widget(); let widgets = view_output!(); ComponentParts { model, widgets } @@ -296,17 +305,13 @@ impl relm4::Component for ClocksFrame { .vf_curve_editing_togglebutton .block_signal(&widgets.vf_curve_editing_signal); widgets - .gpu_locked_clocks_togglebutton - .block_signal(&widgets.gpu_locked_clock_signal); - widgets - .vram_locked_clocks_togglebutton - .block_signal(&widgets.vram_locked_clock_signal); + .locked_clocks_togglebutton + .block_signal(&widgets.locked_clock_signal); - self.core_groups.clear(); - self.vram_groups.clear(); + self.adjustments.clear(); + self.secondary_p_state_clocks.clear(); - self.enable_gpu_locked_clocks.set_value(false); - self.enable_vram_locked_clocks.set_value(false); + self.enable_locked_clocks.set_value(false); self.vf_curve_editing.set_value(vf_curve_is_configured); self.show_nvidia_options = false; @@ -321,26 +326,32 @@ impl relm4::Component for ClocksFrame { let label_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal); let input_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal); - for group in self.all_groups() { - group.add_size_group(label_size_group.clone(), input_size_group.clone()); + for clock_type in self.adjustments.keys() { + self.adjustments.send( + clock_type, + AdjustmentRowMsg::AddSizeGroup { + label_group: label_size_group.clone(), + input_group: input_size_group.clone(), + }, + ); } widgets .vf_curve_editing_togglebutton .unblock_signal(&widgets.vf_curve_editing_signal); widgets - .gpu_locked_clocks_togglebutton - .unblock_signal(&widgets.gpu_locked_clock_signal); - widgets - .vram_locked_clocks_togglebutton - .unblock_signal(&widgets.vram_locked_clock_signal); + .locked_clocks_togglebutton + .unblock_signal(&widgets.locked_clock_signal); self.update_vram_clock_ratio(); sender.input(ClocksFrameMsg::TogglePStatesVisibility); } ClocksFrameMsg::ResetGpuClockOffsets => { - for group in self.core_groups.values() { - group.reset_gpu_clock_offsets(); + for clock_type in self.adjustments.keys() { + if matches!(clock_type, ClockspeedType::GpuClockOffset(_)) { + self.adjustments + .send(clock_type, AdjustmentRowMsg::SetValue(0.0)); + } } } ClocksFrameMsg::VramRatio(vram_ratio) => { @@ -348,14 +359,28 @@ impl relm4::Component for ClocksFrame { self.update_vram_clock_ratio(); } ClocksFrameMsg::TogglePStatesVisibility => { - for group in self.all_groups() { - group.toggle_secondary_visibility( - self.show_all_pstates.value(), - self.show_nvidia_options, - self.enable_gpu_locked_clocks.value(), - self.enable_vram_locked_clocks.value(), - self.vf_curve_editing.value(), - ); + for clock_type in self.adjustments.keys() { + let visible = match clock_type { + ClockspeedType::MaxCoreClock + | ClockspeedType::MinCoreClock + | ClockspeedType::MaxMemoryClock + | ClockspeedType::MinMemoryClock + if self.show_nvidia_options => + { + self.enable_locked_clocks.value() + } + ClockspeedType::GpuClockOffset(_) + if self.show_nvidia_options && self.vf_curve_editing.value() => + { + false + } + _ => { + !self.secondary_p_state_clocks.contains(clock_type) + || self.show_all_pstates.value() + } + }; + self.adjustments + .send(clock_type, AdjustmentRowMsg::SetVisible(visible)); } } } @@ -366,49 +391,54 @@ impl relm4::Component for ClocksFrame { impl ClocksFrame { fn set_clock(&mut self, clock_type: ClockspeedType, data: ClocksData) { - let category = ClockCategory::from_type(clock_type); + if !self.domain.matches(clock_type) { + return; + } - let groups = if category.is_core() { - &mut self.core_groups - } else if category.is_vram() { - &mut self.vram_groups + self.adjustments.insert( + clock_type, + AdjustmentRowInit { + title: data.custom_title.unwrap_or_else(|| clock_title(clock_type)), + info_text: if clock_type == ClockspeedType::VoltageBoost { + fl!(I18N, "gpu-voltage-boost-tooltip") + } else { + String::new() + }, + value: f64::from(data.current), + lower: f64::from(data.min), + upper: f64::from(data.max), + step_increment: get_row_step(clock_type), + ..Default::default() + }, + ); + if data.is_secondary_p_state { + self.secondary_p_state_clocks.insert(clock_type); } else { - unreachable!() - }; - - let mut group = if let Some(group) = groups.get_mut(&category) { - group - } else { - groups.insert(category, ()); - groups.get_mut(&category).unwrap() - }; - - group.set_clock(clock_type, data); - } - - fn all_groups(&self) -> impl Iterator { - self.core_groups.values().chain(self.vram_groups.values()) + self.secondary_p_state_clocks.remove(&clock_type); + } } fn has_any_clocks(&self) -> bool { - self.core_groups.values().any(|group| !group.is_empty()) + !self.adjustments.is_empty() } - fn any_is_secondary(&self) -> bool { - self.all_groups().any(|group| group.has_secondary()) - } - - fn core_any_visible(&self) -> bool { - self.core_groups.values().any(|group| !group.is_empty()) - } - - fn vram_any_visible(&self) -> bool { - self.vram_groups.values().any(|group| !group.is_empty()) + fn has_secondary_p_states(&self) -> bool { + !self.secondary_p_state_clocks.is_empty() } fn update_vram_clock_ratio(&self) { - if let Some(vram_group) = self.vram_groups.get(&ClockCategory::VramClock) { - vram_group.set_value_ratio(self.vram_clock_ratio); + for clock_type in self.adjustments.keys() { + if matches!( + clock_type, + ClockspeedType::MaxMemoryClock + | ClockspeedType::MinMemoryClock + | ClockspeedType::MemClockOffset(_) + ) { + self.adjustments.send( + clock_type, + AdjustmentRowMsg::ValueRatio(self.vram_clock_ratio), + ); + } } } @@ -577,7 +607,7 @@ impl ClocksFrame { ) { let values_len = values.len(); for (i, value) in values.enumerate().rev() { - let is_secondary = i > 0 && i < values_len - 1; + let is_secondary_p_state = i > 0 && i < values_len - 1; self.set_clock( clock_type(i as u8), @@ -585,7 +615,7 @@ impl ClocksFrame { current: value, min, max, - is_secondary, + is_secondary_p_state, ..Default::default() }, ); @@ -596,42 +626,38 @@ impl ClocksFrame { self.show_nvidia_options = true; self.vf_curve_available = !table.gpu_vf_curve.is_empty(); - let locked_clocks = [ - ( + let (clock_range, locked_clocks, min_type, max_type) = match self.domain { + ClockDomain::Gpu => ( table.gpu_clock_range, table.gpu_locked_clocks, ClockspeedType::MinCoreClock, ClockspeedType::MaxCoreClock, - self.enable_gpu_locked_clocks.clone(), ), - ( + ClockDomain::Vram => ( table.vram_clock_range, table.vram_locked_clocks, ClockspeedType::MinMemoryClock, ClockspeedType::MaxMemoryClock, - self.enable_vram_locked_clocks.clone(), ), - ]; + }; - for (clock_range, locked_clocks, min_type, max_type, enable_binding) in locked_clocks { - if let Some((gpu_min, gpu_max)) = clock_range { - let (current_min, current_max) = match locked_clocks { - Some(locked_range) => { - enable_binding.set_value(true); - locked_range - } - None => (gpu_min, gpu_max), - }; + if let Some((gpu_min, gpu_max)) = clock_range { + let (current_min, current_max) = match locked_clocks { + Some(locked_range) => { + self.enable_locked_clocks.set_value(true); + locked_range + } + None => (gpu_min, gpu_max), + }; - self.set_clock( - min_type, - ClocksData::new(current_min as i32, gpu_min as i32, gpu_max as i32), - ); - self.set_clock( - max_type, - ClocksData::new(current_max as i32, gpu_min as i32, gpu_max as i32), - ); - } + self.set_clock( + min_type, + ClocksData::new(current_min as i32, gpu_min as i32, gpu_max as i32), + ); + self.set_clock( + max_type, + ClocksData::new(current_max as i32, gpu_min as i32, gpu_max as i32), + ); } for (pstate, offset) in &table.gpu_offsets { @@ -678,30 +704,20 @@ impl ClocksFrame { } pub fn get_commands(&self) -> Vec { - self.all_groups() - .flat_map(|group| group.get_commands()) - .filter_map(|(clock_type, configured_value)| { + self.adjustments + .iter() + .filter_map(|(clock_type, row)| { + let configured_value = row.get_changed_value().map(|value| value as i32); // If nvidia options are enabled, we always set locked clocks to None or Some let value = if self.show_nvidia_options { match clock_type { - ClockspeedType::MinCoreClock | ClockspeedType::MaxCoreClock => self - .enable_gpu_locked_clocks + ClockspeedType::MinCoreClock + | ClockspeedType::MaxCoreClock + | ClockspeedType::MinMemoryClock + | ClockspeedType::MaxMemoryClock => self + .enable_locked_clocks .value() - .then(|| { - self.core_groups - .get(&ClockCategory::from_type(clock_type)) - .map(|group| group.get_raw_value(clock_type)) - }) - .flatten(), - ClockspeedType::MinMemoryClock | ClockspeedType::MaxMemoryClock => self - .enable_vram_locked_clocks - .value() - .then(|| { - self.vram_groups - .get(&ClockCategory::from_type(clock_type)) - .map(|group| group.get_raw_value(clock_type)) - }) - .flatten(), + .then(|| row.get_value() as i32), _ => Some(configured_value?), } } else { @@ -709,7 +725,7 @@ impl ClocksFrame { }; Some(SetClocksCommand { - r#type: clock_type, + r#type: *clock_type, value, }) }) @@ -717,40 +733,56 @@ impl ClocksFrame { } } -fn nvidia_clock_offset_to_data(clock_info: &NvidiaClockOffset, is_secondary: bool) -> ClocksData { +fn nvidia_clock_offset_to_data( + clock_info: &NvidiaClockOffset, + is_secondary_p_state: bool, +) -> ClocksData { ClocksData { current: clock_info.current, min: clock_info.min, max: clock_info.max, - is_secondary, + is_secondary_p_state, ..Default::default() } } fn clock_title(clock_type: ClockspeedType) -> String { match clock_type { - ClockspeedType::MaxCoreClock => fl!(I18N, "max-gpu-clock"), - ClockspeedType::MaxMemoryClock => fl!(I18N, "max-vram-clock"), + ClockspeedType::MaxCoreClock | ClockspeedType::MaxMemoryClock => fl!(I18N, "max-clock"), ClockspeedType::MaxVoltage => fl!(I18N, "max-gpu-voltage"), - ClockspeedType::MinCoreClock => fl!(I18N, "min-gpu-clock"), - ClockspeedType::MinMemoryClock => fl!(I18N, "min-vram-clock"), + ClockspeedType::MinCoreClock | ClockspeedType::MinMemoryClock => fl!(I18N, "min-clock"), ClockspeedType::MinVoltage => fl!(I18N, "min-gpu-voltage"), ClockspeedType::VoltageOffset => fl!(I18N, "gpu-voltage-offset"), ClockspeedType::VoltageBoost => fl!(I18N, "gpu-voltage-boost"), - ClockspeedType::GpuClockOffset(pstate) => { - fl!(I18N, "gpu-pstate-clock-offset", pstate = pstate) + ClockspeedType::GpuClockOffset(pstate) | ClockspeedType::MemClockOffset(pstate) => { + fl!(I18N, "pstate-clock-offset", pstate = pstate) } - ClockspeedType::MemClockOffset(pstate) => { - fl!(I18N, "vram-pstate-clock-offset", pstate = pstate) + ClockspeedType::GpuVfCurveClock(pstate) | ClockspeedType::MemVfCurveClock(pstate) => { + fl!(I18N, "pstate-clock", pstate = pstate) } - ClockspeedType::GpuVfCurveClock(pstate) => fl!(I18N, "gpu-pstate-clock", pstate = pstate), - ClockspeedType::MemVfCurveClock(pstate) => fl!(I18N, "mem-pstate-clock", pstate = pstate), - ClockspeedType::GpuVfCurveVoltage(pstate) => { - fl!(I18N, "gpu-pstate-clock-voltage", pstate = pstate) - } - ClockspeedType::MemVfCurveVoltage(pstate) => { - fl!(I18N, "mem-pstate-clock-voltage", pstate = pstate) + ClockspeedType::GpuVfCurveVoltage(pstate) | ClockspeedType::MemVfCurveVoltage(pstate) => { + fl!(I18N, "pstate-clock-voltage", pstate = pstate) } ClockspeedType::Reset => unreachable!(), } } + +fn get_row_step(clock_type: ClockspeedType) -> f64 { + match clock_type { + ClockspeedType::MaxCoreClock + | ClockspeedType::MinCoreClock + | ClockspeedType::GpuClockOffset(_) + | ClockspeedType::MaxMemoryClock + | ClockspeedType::MinMemoryClock + | ClockspeedType::MemClockOffset(_) + | ClockspeedType::GpuVfCurveClock(_) + | ClockspeedType::MemVfCurveClock(_) => 5.0, + ClockspeedType::MinVoltage + | ClockspeedType::MaxVoltage + | ClockspeedType::VoltageOffset + | ClockspeedType::VoltageBoost + | ClockspeedType::GpuVfCurveVoltage(_) + | ClockspeedType::MemVfCurveVoltage(_) => 1.0, + ClockspeedType::Reset => unreachable!(), + } +} diff --git a/lact-gui/src/app/pages/oc_page/performance_frame/mod.rs b/lact-gui/src/app/pages/oc_page/performance_frame/mod.rs index ec70a77c..e0ea7e79 100644 --- a/lact-gui/src/app/pages/oc_page/performance_frame/mod.rs +++ b/lact-gui/src/app/pages/oc_page/performance_frame/mod.rs @@ -2,15 +2,11 @@ mod heuristics_list; use crate::{ APP_BROKER, I18N, - app::{components::page_section::PageSection, msg::AppMsg, pages::oc_page::OcPageMsg}, + app::{msg::AppMsg, pages::oc_page::OcPageMsg}, }; +use adw::prelude::*; use amdgpu_sysfs::gpu_handle::{PerformanceLevel, power_profile_mode::PowerProfileModesTable}; -use gtk::{ - StringObject, - gio::prelude::ListModelExt, - glib::object::Cast, - prelude::{BoxExt, ListBoxRowExt, OrientableExt, WidgetExt}, -}; +use gtk::StringObject; use heuristics_list::PowerProfileHeuristicsList; use i18n_embed_fl::fl; use nvml_wrapper::enums::device::PowerMizerMode; @@ -57,11 +53,15 @@ impl relm4::Component for PerformanceFrame { type CommandOutput = (); view! { - PageSection::new("Performance") { + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 10, #[watch] - set_visible: model.performance_level.is_some() || model.active_power_mizer_mode.is_some(), + set_visible: model.performance_level.is_some() + || model.power_profile_modes_table.is_some() + || model.active_power_mizer_mode.is_some(), - append_child = >k::Box { + append = >k::Box { set_orientation: gtk::Orientation::Horizontal, set_spacing: 10, #[watch] @@ -114,7 +114,7 @@ impl relm4::Component for PerformanceFrame { }, }, - append_child = >k::Box { + append = >k::Box { set_orientation: gtk::Orientation::Horizontal, set_spacing: 10, #[watch] @@ -183,7 +183,7 @@ impl relm4::Component for PerformanceFrame { }, }, - append_child = >k::Box { + append = >k::Box { set_orientation: gtk::Orientation::Horizontal, set_spacing: 10, #[watch] diff --git a/lact-gui/src/app/pages/oc_page/power_cap_section.rs b/lact-gui/src/app/pages/oc_page/power_cap_section.rs deleted file mode 100644 index db4591e4..00000000 --- a/lact-gui/src/app/pages/oc_page/power_cap_section.rs +++ /dev/null @@ -1,141 +0,0 @@ -use crate::{ - APP_BROKER, I18N, - app::{ - components::{adjustment_value::AdjustmentValue, page_section::PageSection}, - msg::AppMsg, - pages::PageUpdate, - }, -}; -use gtk::{ - glib::object::ObjectExt, - prelude::{AdjustmentExt, BoxExt, ButtonExt, OrientableExt, RangeExt, ScaleExt, WidgetExt}, -}; -use i18n_embed_fl::fl; -use lact_schema::PowerStats; -use relm4::{ComponentParts, ComponentSender, RelmWidgetExt}; -use std::fmt::Write; - -#[derive(Default)] -pub struct PowerCapSection { - power: PowerStats, - adjustment: AdjustmentValue, - value_text: String, -} - -#[derive(Debug)] -pub enum PowerCapMsg { - Update(PageUpdate), - RefreshText, - Reset, -} - -#[relm4::component(pub)] -impl relm4::Component for PowerCapSection { - type Init = (); - type Input = PowerCapMsg; - type Output = (); - type CommandOutput = (); - - view! { - #[root] - PageSection::new(&fl!(I18N, "power-cap")) { - append_header = >k::Button { - set_label: &fl!(I18N, "default-button"), - connect_clicked => PowerCapMsg::Reset, - - set_halign: gtk::Align::End, - set_hexpand: true, - }, - append_child = >k::Box { - set_orientation: gtk::Orientation::Horizontal, - set_spacing: 10, - - gtk::Label { - #[watch] - set_label: &model.value_text, - }, - - gtk::Scale { - set_orientation: gtk::Orientation::Horizontal, - set_hexpand: true, - set_round_digits: 0, - set_margin_horizontal: 5, - set_draw_value: false, - set_adjustment: adjustment, - }, - } - }, - - #[local_ref] - adjustment -> AdjustmentValue { - connect_value_notify => move |_| { - APP_BROKER.send(AppMsg::SettingsChanged); - } @ value_notify, - connect_value_notify => PowerCapMsg::RefreshText, - connect_upper_notify => PowerCapMsg::RefreshText, - }, - } - - fn init( - _init: Self::Init, - root: Self::Root, - _sender: ComponentSender, - ) -> ComponentParts { - let model = Self::default(); - let adjustment = &model.adjustment; - - let widgets = view_output!(); - - ComponentParts { model, widgets } - } - - fn update_with_view( - &mut self, - widgets: &mut Self::Widgets, - msg: Self::Input, - sender: ComponentSender, - _root: &Self::Root, - ) { - match msg { - PowerCapMsg::Update(PageUpdate::Stats(stats)) => { - // The signal blocking has to be manual, - // because relm's signal block macro feature doesn't seem to work with non-widget objects - self.adjustment.block_signal(&widgets.value_notify); - let power = stats.power.clone(); - - self.adjustment.set_upper(power.cap_max.unwrap_or_default()); - self.adjustment.set_lower(power.cap_min.unwrap_or_default()); - self.adjustment - .set_initial_value(power.cap_current.unwrap_or_default()); - - self.adjustment.unblock_signal(&widgets.value_notify); - - self.power = power; - } - PowerCapMsg::Update(PageUpdate::Info(_)) => (), - PowerCapMsg::RefreshText => { - self.value_text.clear(); - write!( - self.value_text, - "{}/{} {}", - self.adjustment.value(), - self.adjustment.upper(), - fl!(I18N, "watt") - ) - .unwrap(); - } - PowerCapMsg::Reset => { - self.adjustment - .set_value(self.power.cap_default.unwrap_or_default()); - } - } - - self.update_view(widgets, sender); - } -} - -impl PowerCapSection { - pub fn get_user_cap(&self) -> Option { - self.adjustment.get_changed_value(true) - } -} diff --git a/lact-gui/src/app/pages/oc_page/power_frame.rs b/lact-gui/src/app/pages/oc_page/power_frame.rs new file mode 100644 index 00000000..11d6655f --- /dev/null +++ b/lact-gui/src/app/pages/oc_page/power_frame.rs @@ -0,0 +1,177 @@ +use crate::{ + APP_BROKER, I18N, + app::{ + components::{ + adjustment_card::AdjustmentCard, + adjustment_row::{AdjustmentRow, AdjustmentRowInit, AdjustmentRowMsg}, + page_section::PageSection, + }, + msg::AppMsg, + pages::oc_page::{ + OcPageMsg, + performance_frame::{PerformanceFrame, PerformanceFrameMsg}, + }, + utils::ext::RelmDefaultLauchable, + }, +}; +use adw::prelude::*; +use amdgpu_sysfs::gpu_handle::PerformanceLevel; +use i18n_embed_fl::fl; +use lact_schema::PowerStats; +use nvml_wrapper::enums::device::PowerMizerMode; +use relm4::{ComponentController, ComponentParts, ComponentSender, factory::FactoryHashMap}; + +pub struct PowerFrame { + power: PowerStats, + power_row: FactoryHashMap<(), AdjustmentRow<()>>, + performance_frame: relm4::Controller, +} + +#[derive(Debug)] +pub enum PowerFrameMsg { + PowerStats(PowerStats), + Performance(PerformanceFrameMsg), + RefreshVisibility, + Reset, +} + +#[relm4::component(pub)] +impl relm4::Component for PowerFrame { + type Init = (); + type Input = PowerFrameMsg; + type Output = OcPageMsg; + type CommandOutput = (); + + view! { + #[root] + PageSection::new(&fl!(I18N, "power-section")) { + #[watch] + set_visible: model.is_available(), + + append_header = >k::Button { + set_label: &fl!(I18N, "default-button"), + connect_clicked => PowerFrameMsg::Reset, + + set_halign: gtk::Align::End, + set_hexpand: true, + #[watch] + set_visible: !model.power_row.is_empty(), + }, + #[template] + append_child = &AdjustmentCard { + #[template_child] + content { + #[local_ref] + power_row_widget -> gtk::Box { + set_orientation: gtk::Orientation::Vertical, + #[watch] + set_visible: !model.power_row.is_empty(), + }, + + append: model.performance_frame.widget(), + }, + }, + }, + } + + fn init( + _init: Self::Init, + root: Self::Root, + sender: ComponentSender, + ) -> ComponentParts { + let model = Self { + power: PowerStats::default(), + power_row: FactoryHashMap::builder() + .launch_default() + .forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged), + performance_frame: PerformanceFrame::launch_default() + .forward(sender.output_sender(), |msg| msg), + }; + let visibility_sender = sender.clone(); + model + .performance_frame + .widget() + .connect_visible_notify(move |_| { + visibility_sender.input(PowerFrameMsg::RefreshVisibility); + }); + + let power_row_widget = model.power_row.widget(); + let widgets = view_output!(); + + ComponentParts { model, widgets } + } + + fn update_with_view( + &mut self, + widgets: &mut Self::Widgets, + msg: Self::Input, + sender: ComponentSender, + _root: &Self::Root, + ) { + match msg { + PowerFrameMsg::PowerStats(power) => { + self.power_row.clear(); + + if let Some(value) = power.cap_current { + self.power_row.insert( + (), + AdjustmentRowInit { + title: format!("{} ({})", fl!(I18N, "power-cap"), fl!(I18N, "watt")), + value, + lower: power.cap_min.unwrap_or_default(), + upper: power.cap_max.unwrap_or_default(), + ..Default::default() + }, + ); + } + + self.power = power; + } + PowerFrameMsg::Performance(msg) => { + self.performance_frame.emit(msg); + } + PowerFrameMsg::RefreshVisibility => (), + PowerFrameMsg::Reset => { + if !self.power_row.is_empty() { + self.power_row.send( + &(), + AdjustmentRowMsg::SetValue(self.power.cap_default.unwrap_or_default()), + ); + } + } + } + + self.update_view(widgets, sender); + } +} + +impl PowerFrame { + fn is_available(&self) -> bool { + !self.power_row.is_empty() || self.performance_frame.widget().get_visible() + } + + pub fn get_user_cap(&self) -> Option { + self.power_row + .get(&())? + .get_changed_value() + .filter(|value| *value != 0.0) + } + + pub fn performance_level(&self) -> Option { + self.performance_frame.model().performance_level() + } + + pub fn active_power_mizer_mode(&self) -> Option { + self.performance_frame.model().active_power_mizer_mode() + } + + pub fn power_profile_mode(&self) -> Option { + self.performance_frame.model().power_profile_mode() + } + + pub fn power_profile_mode_custom_heuristics(&self) -> Vec>> { + self.performance_frame + .model() + .power_profile_mode_custom_heuristics() + } +} diff --git a/lact-gui/src/app/pages/oc_page/power_states/power_states_frame.rs b/lact-gui/src/app/pages/oc_page/power_states/power_states_frame.rs index 7cbd6d31..853ce997 100644 --- a/lact-gui/src/app/pages/oc_page/power_states/power_states_frame.rs +++ b/lact-gui/src/app/pages/oc_page/power_states/power_states_frame.rs @@ -3,15 +3,15 @@ use super::power_states_list::{PowerStatesListMsg, PowerStatesListOptions}; use crate::{ APP_BROKER, I18N, app::{ - components::page_section_expander::PageSectionExpander, msg::AppMsg, - pages::oc_page::OcPageMsg, utils::ext::RelmLaunchable as _, + components::{adjustment_card::AdjustmentCard, page_section::PageSection}, + msg::AppMsg, + pages::oc_page::OcPageMsg, + utils::ext::RelmLaunchable as _, }, }; +use adw::prelude::*; use amdgpu_sysfs::gpu_handle::{PerformanceLevel, PowerLevelKind}; -use gtk::{ - glib::{SignalHandlerId, object::ObjectExt}, - prelude::{BoxExt, CheckButtonExt, OrientableExt, WidgetExt}, -}; +use gtk::glib::{SignalHandlerId, object::ObjectExt}; use i18n_embed_fl::fl; use indexmap::IndexMap; use lact_schema::{DeviceStats, PowerStates}; @@ -57,44 +57,61 @@ impl relm4::SimpleComponent for PowerStatesFrame { type Output = OcPageMsg; view! { - PageSectionExpander::new(&fl!(I18N, "pstates")) { - append_expandable = >k::Box { - set_orientation: gtk::Orientation::Vertical, - set_spacing: 5, - - gtk::CheckButton { - set_label: Some(&fl!(I18N, "enable-pstate-config")), - #[watch] - #[block_signal(configured_toggled_handler)] - set_active: model.states_configuration_enabled.value(), - - connect_toggled[sender] => move |button| { - sender.input(PowerStatesFrameMsg::ConfiguredToggled { - configured: button.is_active(), - }); - } @ configured_toggled_handler, - + PageSection::new(&fl!(I18N, "pstates")) { + #[template] + append_child = &AdjustmentCard { + #[template_child] + advanced_features { #[watch] set_visible: model.performance_level.is_some(), }, - gtk::Box { - set_spacing: 10, - set_orientation: gtk::Orientation::Horizontal, + #[template_child] + controls { + gtk::ToggleButton { + set_halign: gtk::Align::Start, + add_css_class: "adjustment-card-option-toggle", - gtk::Box { #[watch] - set_visible: model.has_core_states, - append = model.core_states_list.widget(), - }, + #[block_signal(configured_toggled_handler)] + set_active: model.states_configuration_enabled.value(), - gtk::Box { - #[watch] - set_visible: model.has_vram_states, - append = model.vram_states_list.widget(), + connect_toggled[sender] => move |button| { + sender.input(PowerStatesFrameMsg::ConfiguredToggled { + configured: button.is_active(), + }); + } @ configured_toggled_handler, + + #[wrap(Some)] + set_child = >k::Box { + gtk::Label { + set_label: &fl!(I18N, "enable-pstate-config"), + }, + }, }, - } - } + }, + + #[template_child] + content { + gtk::Box { + set_spacing: 10, + set_orientation: gtk::Orientation::Horizontal, + set_homogeneous: true, + + gtk::Box { + #[watch] + set_visible: model.has_core_states, + append = model.core_states_list.widget(), + }, + + gtk::Box { + #[watch] + set_visible: model.has_vram_states, + append = model.vram_states_list.widget(), + }, + }, + }, + }, } } diff --git a/lact-gui/src/app/pages/oc_page/vf_curve.rs b/lact-gui/src/app/pages/oc_page/vf_curve.rs index 68c25e15..ce69fe9f 100644 --- a/lact-gui/src/app/pages/oc_page/vf_curve.rs +++ b/lact-gui/src/app/pages/oc_page/vf_curve.rs @@ -542,14 +542,14 @@ impl VfCurveEditor { if let Some((min_freq, max_freq)) = self.locked_clocks_range.get() { let mut curves = vec![( [(x_spec.start, max_freq), (x_spec.end, max_freq)], - fl!(I18N, "max-gpu-clock"), + fl!(I18N, "max-clock"), colors.error, )]; if min_freq >= y_spec.start { curves.push(( [(x_spec.start, min_freq), (x_spec.end, min_freq)], - fl!(I18N, "min-gpu-clock"), + fl!(I18N, "min-clock"), colors.warning, )); } diff --git a/lact-gui/src/app/utils/styles.rs b/lact-gui/src/app/utils/styles.rs index 458f5585..f17be971 100644 --- a/lact-gui/src/app/utils/styles.rs +++ b/lact-gui/src/app/utils/styles.rs @@ -8,6 +8,9 @@ use std::cell::RefCell; use crate::{CONFIG, app::utils::color_scheme::AppColorScheme}; pub const COMBINED_CSS: &str = include_str!(concat!(env!("OUT_DIR"), "/combined.css")); +// cosmic gtk4 override has 800 prio, so we have to beat it +const BREEZE_CSS_PRIORITY: u32 = 801; +pub const APPLICATION_CSS_PRIORITY: u32 = 802; macro_rules! include_theme_str { ($file:literal) => { @@ -64,7 +67,7 @@ pub fn apply_theme(theme: AppTheme) -> anyhow::Result<()> { #[allow(deprecated)] provider.load_from_data(css); - style_context_add_provider_for_display(&display, &provider, 900); + style_context_add_provider_for_display(&display, &provider, BREEZE_CSS_PRIORITY); EXISTING_STYLE_PROVIDER.set(Some(provider)); }