From 37f7bc0842581426cff01df3e8fbb4487876ba35 Mon Sep 17 00:00:00 2001 From: Roman Makarov Date: Sat, 10 Oct 2026 10:57:56 +0200 Subject: [PATCH] refactor: migrate thermals-page to adjustmentRow (#1208) * move adjustmentRow refactoring * refactor back to FactoryComponent * migrate thermals page to adjustmentRow * remove thermal page tabs * block signal * basic fixes * layout fixes * fanCruveFrame: extract chart state into it's own struct * don't create uneeded adjustments instead of hiding them --- lact-gui/src/app/pages/thermals_page.css | 22 + lact-gui/src/app/pages/thermals_page.rs | 765 +++++++----------- .../pages/thermals_page/fan_curve_frame.rs | 410 ++++------ 3 files changed, 487 insertions(+), 710 deletions(-) create mode 100644 lact-gui/src/app/pages/thermals_page.css diff --git a/lact-gui/src/app/pages/thermals_page.css b/lact-gui/src/app/pages/thermals_page.css new file mode 100644 index 00000000..d3b3977d --- /dev/null +++ b/lact-gui/src/app/pages/thermals_page.css @@ -0,0 +1,22 @@ +.thermals-page .adjustment-row > scale { + margin-left: 10px; + margin-right: 10px; +} + +.thermals-page .adjustment-row > spinbutton { + min-width: 120px; +} + +.fan-control-section .adjustment-list { + background: transparent; + border-radius: 0; + box-shadow: none; +} + +.fan-control-section .adjustment-list > row { + border-radius: 0; +} + +.fan-control-section .fan-control-option { + margin: 5px 10px; +} diff --git a/lact-gui/src/app/pages/thermals_page.rs b/lact-gui/src/app/pages/thermals_page.rs index 743c5175..50ba2cc4 100644 --- a/lact-gui/src/app/pages/thermals_page.rs +++ b/lact-gui/src/app/pages/thermals_page.rs @@ -7,24 +7,22 @@ use crate::app::pages::PageUpdate; use crate::{ APP_BROKER, I18N, app::{ - components::{adjustment_value::AdjustmentValue, page_section::PageSection}, + components::{ + adjustment_card::AdjustmentCard, + adjustment_row::{AdjustmentRow, AdjustmentRowInit, AdjustmentRowMsg}, + page_section::PageSection, + }, msg::AppMsg, - utils::ext::{RelmLaunchable as _, make_event_controller_no_scroll}, + utils::ext::RelmLaunchable as _, }, }; +use adw::prelude::*; use amdgpu_sysfs::gpu_handle::fan_control::FanInfo; use anyhow::anyhow; use fan_curve_frame::{ CurveSetupMsg, DEFAULT_SPEED_RANGE, DEFAULT_TEMP_RANGE, FanCurveFrame, FanCurveFrameMsg, }; -use gtk::{ - Adjustment, - glib::{ - self, SignalHandlerId, - object::{Cast, ObjectExt}, - }, - prelude::{AdjustmentExt, BoxExt, ButtonExt, OrientableExt, RangeExt, ScaleExt, WidgetExt}, -}; +use gtk::glib::{self, SignalHandlerId}; use i18n_embed_fl::fl; use lact_schema::{ DeviceFlag, FanControlMode, @@ -32,76 +30,46 @@ use lact_schema::{ default_fan_curve, }; use relm4::{ - ComponentController, ComponentParts, ComponentSender, RelmObjectExt, RelmWidgetExt, - binding::{Binding, BoolBinding, ConnectBinding, StringBinding}, + ComponentController, ComponentParts, ComponentSender, RelmWidgetExt, WidgetTemplate, + binding::{Binding, BoolBinding, ConnectBinding}, + factory::FactoryHashMap, }; use std::collections::HashSet; use std::sync::Arc; -use std::{cell::Cell, rc::Rc}; - -const AUTO_PAGE: &str = "automatic"; -const CURVE_PAGE: &str = "curve"; -const STATIC_PAGE: &str = "static"; pub struct ThermalsPage { stats_section: relm4::Controller, + pmfw_rows: FactoryHashMap>, + nvidia_target_temperature: FactoryHashMap<(), AdjustmentRow<()>>, + zero_rpm_temperature: FactoryHashMap<(), AdjustmentRow<()>>, + static_speed: FactoryHashMap<(), AdjustmentRow<()>>, fan_curve_frame: relm4::Controller, - selected_mode: StringBinding, + selected_mode: Option, custom_control_supported: bool, has_fan_speed: bool, has_pmfw: bool, has_auto_threshold: bool, - pmfw_options: PmfwOptions, - nvidia_thermal_options: NvidiaThermalOptions, - option_change_signals: Vec<(glib::Object, SignalHandlerId)>, - - static_speed_adj: Adjustment, -} - -#[derive(Clone, Default)] -struct PmfwOptions { - target_temperature: AdjustmentValue, - acoustic_limit: AdjustmentValue, - acoustic_target: AdjustmentValue, - minimum_pwm: AdjustmentValue, - zero_rpm_temperature: AdjustmentValue, - zero_rpm_available: Rc>, + label_size_group: gtk::SizeGroup, + input_size_group: gtk::SizeGroup, zero_rpm: BoolBinding, + zero_rpm_available: bool, + zero_rpm_change_signal: SignalHandlerId, + target_temperature_default: Option, } -impl PmfwOptions { - fn adjustments(&self) -> [&AdjustmentValue; 5] { - [ - &self.target_temperature, - &self.acoustic_limit, - &self.acoustic_target, - &self.minimum_pwm, - &self.zero_rpm_temperature, - ] - } - - fn is_empty(&self) -> bool { - self.adjustments().iter().all(|adj| adj_is_empty(adj)) && !self.zero_rpm_available.get() - } -} - -#[derive(Clone, Default)] -struct NvidiaThermalOptions { - target_temperature: AdjustmentValue, - target_temperaure_default: Option, -} - -impl NvidiaThermalOptions { - fn adjustments(&self) -> [&AdjustmentValue; 1] { - [&self.target_temperature] - } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ThermalSetting { + TargetTemperature, + AcousticLimit, + AcousticTarget, + MinimumPwm, } #[derive(Debug)] pub enum ThermalsPageMsg { Update { update: PageUpdate, initial: bool }, - FanModeSelected, + FanModeSelected(Option), RestNvidiaOptions, } @@ -113,8 +81,10 @@ impl relm4::Component for ThermalsPage { type CommandOutput = (); view! { + #[root] gtk::Box { set_orientation: gtk::Orientation::Vertical, + add_css_class: "thermals-page", set_spacing: 15, set_margin_all: 15, set_margin_top: 20, // align with gpu picker @@ -123,8 +93,9 @@ impl relm4::Component for ThermalsPage { PageSection { set_name: fl!(I18N, "thresholds-section"), + set_hide_visible_container: true, #[watch] - set_visible: !adj_is_empty(&model.nvidia_thermal_options.target_temperature), + set_visible: !model.nvidia_target_temperature.is_empty(), append_header = >k::Button { set_label: &fl!(I18N, "default-button"), @@ -133,227 +104,120 @@ impl relm4::Component for ThermalsPage { connect_clicked => ThermalsPageMsg::RestNvidiaOptions, }, - #[template] - append_child = &FanSettingRow { - #[template_child] - label { - set_label: &fl!(I18N, "target-temp"), - set_size_group: &label_size_group, - }, - - #[template_child] - scale { - set_adjustment: &model.nvidia_thermal_options.target_temperature, - }, - - #[template_child] - spinbutton { - set_adjustment: &model.nvidia_thermal_options.target_temperature, - set_size_group: &spin_size_group, - }, - }, + append_child = model.nvidia_target_temperature.widget(), }, PageSection { set_name: fl!(I18N, "fan-control-section"), + add_css_class: "fan-control-section", // Disable fan configuration when overdrive is disabled on GPUs that have PMFW (RDNA3+) #[watch] set_sensitive: model.custom_control_supported, - append_child = >k::StackSwitcher { - set_stack: Some(&stack), + append_child = >k::Box { + add_css_class: "linked", + set_homogeneous: true, + + #[name = "automatic_mode"] + gtk::ToggleButton { + set_label: &fl!(I18N, "auto-page"), + #[watch] + #[block_signal(automatic_mode_signal)] + set_active: model.selected_mode.is_none(), + connect_toggled[sender] => move |button| { + if button.is_active() { + sender.input(ThermalsPageMsg::FanModeSelected(None)); + } + } @ automatic_mode_signal, + }, + + gtk::ToggleButton { + set_label: &fl!(I18N, "curve-page"), + set_group: Some(&automatic_mode), + #[watch] + #[block_signal(curve_mode_signal)] + set_active: model.selected_mode == Some(FanControlMode::Curve), + connect_toggled[sender] => move |button| { + if button.is_active() { + sender.input(ThermalsPageMsg::FanModeSelected(Some(FanControlMode::Curve))); + } + } @ curve_mode_signal, + }, + + gtk::ToggleButton { + set_label: &fl!(I18N, "static-page"), + set_group: Some(&automatic_mode), + #[watch] + #[block_signal(static_mode_signal)] + set_active: model.selected_mode == Some(FanControlMode::Static), + connect_toggled[sender] => move |button| { + if button.is_active() { + sender.input(ThermalsPageMsg::FanModeSelected(Some(FanControlMode::Static))); + } + } @ static_mode_signal, + }, }, - #[name = "stack"] - append_child = >k::Stack { - set_vexpand: false, - set_vhomogeneous: false, + append_child = >k::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 5, #[watch] set_visible: model.fan_settings_available(), - add_titled[Some(AUTO_PAGE), &fl!(I18N, "auto-page")] = >k::Box { - set_orientation: gtk::Orientation::Vertical, + model.pmfw_rows.widget().clone() -> gtk::ListBox { + #[watch] + set_visible: model.selected_mode.is_none() && !model.pmfw_rows.is_empty(), + }, + + model.fan_curve_frame.widget().clone() -> gtk::Box { + #[watch] + set_visible: model.selected_mode == Some(FanControlMode::Curve), + }, + + model.static_speed.widget().clone() -> gtk::ListBox { + #[watch] + set_visible: model.selected_mode == Some(FanControlMode::Static), + }, + + gtk::Box { + add_css_class: "fan-control-option", set_spacing: 5, + #[watch] + set_visible: model.selected_mode != Some(FanControlMode::Static) + && model.zero_rpm_available, - #[template] - FanSettingRow { - #[watch] - set_visible: !adj_is_empty(&model.pmfw_options.target_temperature), - - #[template_child] - label { - set_label: &fl!(I18N, "target-temp"), - set_size_group: &label_size_group, - }, - - #[template_child] - scale { - set_adjustment: &model.pmfw_options.target_temperature, - }, - - #[template_child] - spinbutton { - set_adjustment: &model.pmfw_options.target_temperature, - set_size_group: &spin_size_group, - }, + gtk::Label { + set_label: &fl!(I18N, "zero-rpm"), + set_size_group: &model.label_size_group, + set_xalign: 0.0, }, - #[template] - FanSettingRow { - #[watch] - set_visible: !adj_is_empty(&model.pmfw_options.acoustic_limit), - - #[template_child] - label { - set_label: &fl!(I18N, "acoustic-limit"), - set_size_group: &label_size_group, - }, - - #[template_child] - scale { - set_adjustment: &model.pmfw_options.acoustic_limit, - }, - - #[template_child] - spinbutton { - set_adjustment: &model.pmfw_options.acoustic_limit, - set_size_group: &spin_size_group, - }, - }, - - #[template] - FanSettingRow { - #[watch] - set_visible: !adj_is_empty(&model.pmfw_options.acoustic_target), - - #[template_child] - label { - set_label: &fl!(I18N, "acoustic-target"), - set_size_group: &label_size_group, - }, - - #[template_child] - scale { - set_adjustment: &model.pmfw_options.acoustic_target, - }, - - #[template_child] - spinbutton { - set_adjustment: &model.pmfw_options.acoustic_target, - set_size_group: &spin_size_group, - }, - }, - - #[template] - FanSettingRow { - #[watch] - set_visible: !adj_is_empty(&model.pmfw_options.minimum_pwm), - - #[template_child] - label { - set_label: &fl!(I18N, "min-fan-speed"), - set_size_group: &label_size_group, - }, - - #[template_child] - scale { - set_adjustment: &model.pmfw_options.minimum_pwm, - }, - - #[template_child] - spinbutton { - set_adjustment: &model.pmfw_options.minimum_pwm, - set_size_group: &spin_size_group, - }, - }, - - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_spacing: 5, - #[watch] - set_visible: model.pmfw_options.zero_rpm_available.get(), - - gtk::Label { - set_label: &fl!(I18N, "zero-rpm"), - set_xalign: 0.0, - set_size_group: &label_size_group, - }, - - gtk::Switch { - bind: &model.pmfw_options.zero_rpm, - set_hexpand: true, - set_halign: gtk::Align::End, - }, - }, - - #[template] - FanSettingRow { - #[watch] - set_visible: !adj_is_empty(&model.pmfw_options.zero_rpm_temperature), - - #[template_child] - label { - set_label: &fl!(I18N, "zero-rpm-stop-temp"), - set_size_group: &label_size_group, - }, - - #[template_child] - scale { - set_adjustment: &model.pmfw_options.zero_rpm_temperature, - }, - - #[template_child] - spinbutton { - set_adjustment: &model.pmfw_options.zero_rpm_temperature, - set_size_group: &spin_size_group, - }, - }, - - gtk::Button { - set_label: &fl!(I18N, "reset-now-button"), + gtk::Switch { + bind: &model.zero_rpm, + set_hexpand: true, set_halign: gtk::Align::End, - set_margin_vertical: 5, - set_tooltip_text: Some(&fl!(I18N, "pmfw-reset-warning")), - add_css_class: "destructive-action", - set_size_group: &spin_size_group, - #[watch] - set_visible: !model.pmfw_options.is_empty(), - connect_clicked => move |_| { - APP_BROKER.send(AppMsg::ResetPmfw); - } - }, - }, - add_titled[Some(CURVE_PAGE), &fl!(I18N, "curve-page")] = model.fan_curve_frame.widget(), - add_titled[Some(STATIC_PAGE), &fl!(I18N, "static-page")] = >k::Box { - set_valign: gtk::Align::Start, - set_orientation: gtk::Orientation::Vertical, - set_spacing: 5, - - #[template] - #[name = "static_speed_row"] - FanSettingRow { - #[template_child] - label { - set_label: &fl!(I18N, "static-speed"), - }, - - #[template_child] - scale { - set_adjustment: &model.static_speed_adj, - connect_value_changed => move |_| { - APP_BROKER.send(AppMsg::SettingsChanged); - } @ static_speed_changed_signal, - }, - - #[template_child] - spinbutton { - set_adjustment: &model.static_speed_adj, - }, }, }, - add_binding: (&model.selected_mode, "visible-child-name"), - connect_visible_child_name_notify => ThermalsPageMsg::FanModeSelected @ mode_selected_signal, + model.zero_rpm_temperature.widget().clone() -> gtk::ListBox { + #[watch] + set_visible: model.selected_mode != Some(FanControlMode::Static) + && !model.zero_rpm_temperature.is_empty(), + }, + + gtk::Button { + set_label: &fl!(I18N, "reset-now-button"), + set_size_group: &model.input_size_group, + set_halign: gtk::Align::End, + set_margin_vertical: 5, + set_tooltip_text: Some(&fl!(I18N, "pmfw-reset-warning")), + add_css_class: "destructive-action", + #[watch] + set_visible: model.selected_mode.is_none() && model.has_pmfw_options(), + connect_clicked => move |_| { + APP_BROKER.send(AppMsg::ResetPmfw); + } + }, } } }, @@ -362,52 +226,13 @@ impl relm4::Component for ThermalsPage { fn init( _init: Self::Init, root: Self::Root, - _sender: ComponentSender, + sender: ComponentSender, ) -> ComponentParts { - let pmfw_options = PmfwOptions::default(); - let nvidia_thermal_options = NvidiaThermalOptions::default(); - - for adj in pmfw_options.adjustments() { - adj.set_step_increment(1.0); - adj.set_page_increment(5.0); - } - - for adj in nvidia_thermal_options.adjustments() { - adj.set_step_increment(1.0); - adj.set_page_increment(5.0); - } - - let pmfw_change_signals = pmfw_options - .adjustments() - .into_iter() - .map(|adj| { - let signal = adj.connect_value_changed(|_| { - APP_BROKER.send(AppMsg::SettingsChanged); - }); - (adj.clone().upcast(), signal) - }) - .chain([( - pmfw_options.zero_rpm.clone().upcast(), - pmfw_options.zero_rpm.connect_value_notify(|_| { - APP_BROKER.send(AppMsg::SettingsChanged); - }), - )]); - - let nvidia_thermal_change_signals = - nvidia_thermal_options.adjustments().into_iter().map(|adj| { - let signal = adj.connect_value_changed(|_| { - APP_BROKER.send(AppMsg::SettingsChanged); - }); - (adj.clone().upcast(), signal) - }); - - let option_change_signals = pmfw_change_signals - .chain(nvidia_thermal_change_signals) - .collect(); - - let fan_curve_frame = FanCurveFrame::builder() - .launch(pmfw_options.clone()) - .detach(); + let zero_rpm = BoolBinding::new(false); + let zero_rpm_change_signal = zero_rpm.connect_value_notify(|_| { + APP_BROKER.send(AppMsg::SettingsChanged); + }); + let fan_curve_frame = FanCurveFrame::detach(()); let stats_section = GpuStatsSection::detach(GpuStatsSectionConfig { stats: HashSet::from([ GpuStat::Throttling, @@ -418,22 +243,33 @@ impl relm4::Component for ThermalsPage { }); let model = Self { + pmfw_rows: FactoryHashMap::builder() + .launch(AdjustmentCard::init(()).content.clone()) + .forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged), + nvidia_target_temperature: FactoryHashMap::builder() + .launch(AdjustmentCard::init(()).content.clone()) + .forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged), + zero_rpm_temperature: FactoryHashMap::builder() + .launch(AdjustmentCard::init(()).content.clone()) + .forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged), + static_speed: FactoryHashMap::builder() + .launch(AdjustmentCard::init(()).content.clone()) + .forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged), stats_section, fan_curve_frame, - pmfw_options, - nvidia_thermal_options, - option_change_signals, + label_size_group: gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal), + input_size_group: gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal), + zero_rpm, + zero_rpm_available: false, + zero_rpm_change_signal, + target_temperature_default: None, custom_control_supported: false, has_fan_speed: false, has_pmfw: false, has_auto_threshold: false, - static_speed_adj: Adjustment::new(50.0, 0.0, 100.0, 1.0, 5.0, 0.0), - selected_mode: StringBinding::new(AUTO_PAGE), + selected_mode: None, }; - let label_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal); - let spin_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal); - let widgets = view_output!(); ComponentParts { model, widgets } @@ -464,17 +300,8 @@ impl relm4::Component for ThermalsPage { self.has_fan_speed = stats.fan.pwm_current.is_some() || stats.fan.speed_current.is_some(); if initial { - let page_name = match stats.fan.control_mode { - Some(mode) if stats.fan.control_enabled => match mode { - FanControlMode::Static => STATIC_PAGE, - FanControlMode::Curve => CURVE_PAGE, - }, - _ => AUTO_PAGE, - }; - - widgets.stack.block_signal(&widgets.mode_selected_signal); - self.selected_mode.set(page_name.to_owned()); - widgets.stack.unblock_signal(&widgets.mode_selected_signal); + self.selected_mode = + stats.fan.control_mode.filter(|_| stats.fan.control_enabled); let speed_range = stats .fan @@ -487,22 +314,22 @@ impl relm4::Component for ThermalsPage { }) .unwrap_or(DEFAULT_SPEED_RANGE); - widgets - .static_speed_row - .scale - .block_signal(&widgets.static_speed_changed_signal); + self.pmfw_rows.clear(); + self.nvidia_target_temperature.clear(); + self.zero_rpm_temperature.clear(); + self.static_speed.clear(); - self.static_speed_adj - .set_lower((*speed_range.start() as f64 * 100.0).round()); - self.static_speed_adj - .set_upper((*speed_range.end() as f64 * 100.0).round()); - self.static_speed_adj - .set_value((stats.fan.static_speed.unwrap_or(0.5) * 100.0).into()); - - widgets - .static_speed_row - .scale - .unblock_signal(&widgets.static_speed_changed_signal); + self.static_speed.insert( + (), + AdjustmentRowInit { + title: glib::markup_escape_text(&fl!(I18N, "static-speed")).into(), + value: (stats.fan.static_speed.unwrap_or(0.5) * 100.0).into(), + lower: (*speed_range.start() as f64 * 100.0).round(), + upper: (*speed_range.end() as f64 * 100.0).round(), + page_increment: 5.0, + ..Default::default() + }, + ); let temperature_range = stats .fan @@ -525,49 +352,87 @@ impl relm4::Component for ThermalsPage { self.fan_curve_frame.emit(FanCurveFrameMsg::Curve(msg)); let info = stats.fan.pmfw_info; - let pmfw_options = &mut self.pmfw_options; - let nvidia_thermal_options = &mut self.nvidia_thermal_options; - - for (obj, signal) in &self.option_change_signals { - obj.block_signal(signal); + let lower_label_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal); + let upper_label_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal); + for (setting, title, info) in [ + ( + ThermalSetting::TargetTemperature, + fl!(I18N, "target-temp"), + info.target_temp, + ), + ( + ThermalSetting::AcousticLimit, + fl!(I18N, "acoustic-limit"), + info.acoustic_limit, + ), + ( + ThermalSetting::AcousticTarget, + fl!(I18N, "acoustic-target"), + info.acoustic_target, + ), + ( + ThermalSetting::MinimumPwm, + fl!(I18N, "min-fan-speed"), + info.minimum_pwm, + ), + ] { + if let Some(init) = fan_info_init(title, info) { + self.pmfw_rows.insert(setting, init); + self.pmfw_rows.send( + &setting, + AdjustmentRowMsg::AddSizeGroup { + label_group: self.label_size_group.clone(), + input_group: self.input_size_group.clone(), + lower_label_group: lower_label_group.clone(), + upper_label_group: upper_label_group.clone(), + }, + ); + } } - - set_fan_info(&pmfw_options.acoustic_limit, info.acoustic_limit); - set_fan_info(&pmfw_options.acoustic_target, info.acoustic_target); - set_fan_info(&pmfw_options.minimum_pwm, info.minimum_pwm); - set_fan_info(&pmfw_options.target_temperature, info.target_temp); - set_fan_info( - &nvidia_thermal_options.target_temperature, - stats.nvidia_thermal_info.target_temp, - ); - nvidia_thermal_options.target_temperaure_default = + for (rows, title, info) in [ + ( + &mut self.nvidia_target_temperature, + fl!(I18N, "target-temp"), + stats.nvidia_thermal_info.target_temp, + ), + ( + &mut self.zero_rpm_temperature, + fl!(I18N, "zero-rpm-stop-temp"), + info.zero_rpm_temperature, + ), + ] { + if let Some(init) = fan_info_init(title, info) { + rows.insert((), init); + rows.send( + &(), + AdjustmentRowMsg::AddSizeGroup { + label_group: self.label_size_group.clone(), + input_group: self.input_size_group.clone(), + lower_label_group: lower_label_group.clone(), + upper_label_group: upper_label_group.clone(), + }, + ); + } + } + self.target_temperature_default = stats.nvidia_thermal_info.target_temp_default; - set_fan_info( - &pmfw_options.zero_rpm_temperature, - info.zero_rpm_temperature, - ); - - pmfw_options - .zero_rpm_available - .set(info.zero_rpm_enable.is_some()); - pmfw_options - .zero_rpm - .set(info.zero_rpm_enable.unwrap_or(false)); - - for (obj, signal) in &self.option_change_signals { - obj.unblock_signal(signal); - } + self.zero_rpm_available = info.zero_rpm_enable.is_some(); + self.zero_rpm.block_signal(&self.zero_rpm_change_signal); + self.zero_rpm.set(info.zero_rpm_enable.unwrap_or(false)); + self.zero_rpm.unblock_signal(&self.zero_rpm_change_signal); } } }, - ThermalsPageMsg::FanModeSelected => { + ThermalsPageMsg::FanModeSelected(mode) => { + self.selected_mode = mode; APP_BROKER.send(AppMsg::SettingsChanged); } ThermalsPageMsg::RestNvidiaOptions => { - if let Some(default) = self.nvidia_thermal_options.target_temperaure_default { - self.nvidia_thermal_options - .target_temperature - .set_value(default as f64); + if let Some(default) = self.target_temperature_default { + if !self.nvidia_target_temperature.is_empty() { + self.nvidia_target_temperature + .send(&(), AdjustmentRowMsg::SetValue(default as f64)); + } } else { APP_BROKER.send(AppMsg::Error(Arc::new(anyhow!( "No default target temperature present" @@ -582,23 +447,18 @@ impl relm4::Component for ThermalsPage { impl ThermalsPage { fn fan_settings_available(&self) -> bool { - self.has_fan_speed - && (self.selected_mode.value() != AUTO_PAGE || !self.pmfw_options.is_empty()) + self.has_fan_speed && (self.selected_mode.is_some() || self.has_pmfw_options()) } pub fn apply_config(&self, config: &mut GpuConfig) { - let selected_page = self.selected_mode.value(); - - if selected_page == AUTO_PAGE { - config.fan_control_enabled = false; - } else { + if let Some(mode) = self.selected_mode { config.fan_control_enabled = true; let fan_settings = config .fan_control_settings .get_or_insert_with(FanControlSettings::default); - match selected_page.as_str() { - CURVE_PAGE => { + match mode { + FanControlMode::Curve => { fan_settings.mode = FanControlMode::Curve; let fan_curve_model = self.fan_curve_frame.model(); @@ -614,103 +474,78 @@ impl ThermalsPage { fan_settings.temperature_key = temp_key; } } - STATIC_PAGE => { + FanControlMode::Static => { fan_settings.mode = FanControlMode::Static; - fan_settings.static_speed = self.static_speed_adj.value() as f32 / 100.0; + fan_settings.static_speed = self + .static_speed + .get(&()) + .map(|row| row.get_value() as f32 / 100.0) + .unwrap_or(0.5); } - _ => unreachable!("Invalid fan control page selected"), } + } else { + config.fan_control_enabled = false; } - let pmfw = &self.pmfw_options; let pmfw_config = &mut config.pmfw_options; - let options = [ - (&pmfw.acoustic_limit, &mut pmfw_config.acoustic_limit), - (&pmfw.acoustic_target, &mut pmfw_config.acoustic_target), + for (setting, config_value) in [ ( - &pmfw.target_temperature, + ThermalSetting::AcousticLimit, + &mut pmfw_config.acoustic_limit, + ), + ( + ThermalSetting::AcousticTarget, + &mut pmfw_config.acoustic_target, + ), + ( + ThermalSetting::TargetTemperature, &mut pmfw_config.target_temperature, ), - (&pmfw.minimum_pwm, &mut pmfw_config.minimum_pwm), - ( - &pmfw.zero_rpm_temperature, - &mut pmfw_config.zero_rpm_threshold, - ), - ]; - - for (adj, config_value) in options { - if let Some(value) = adj.get_changed_value(false) { + (ThermalSetting::MinimumPwm, &mut pmfw_config.minimum_pwm), + ] { + if let Some(value) = self + .pmfw_rows + .get(&setting) + .and_then(|row| row.get_changed_value()) + { *config_value = Some(value as u32); } } - - if pmfw.zero_rpm_available.get() { - pmfw_config.zero_rpm = Some(pmfw.zero_rpm.value()); - } - if let Some(value) = self - .nvidia_thermal_options - .target_temperature - .get_changed_value(false) + .zero_rpm_temperature + .get(&()) + .and_then(|row| row.get_changed_value()) + { + pmfw_config.zero_rpm_threshold = Some(value as u32); + } + if let Some(value) = self + .nvidia_target_temperature + .get(&()) + .and_then(|row| row.get_changed_value()) { config.nvidia_thermal_options.target_temperature = Some(value as u32); } - } -} - -#[relm4::widget_template(pub)] -impl relm4::WidgetTemplate for FanSettingRow { - view! { - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_spacing: 5, - - #[name = "label"] - gtk::Label { - set_xalign: 0.0, - }, - - #[name = "scale"] - gtk::Scale { - set_orientation: gtk::Orientation::Horizontal, - set_hexpand: true, - set_digits: 0, - set_round_digits: 0, - set_value_pos: gtk::PositionType::Right, - set_margin_horizontal: 5, - add_controller = make_event_controller_no_scroll(), - }, - - #[name = "spinbutton"] - gtk::SpinButton { - set_width_request: 120, - add_controller = make_event_controller_no_scroll(), - }, - }, - } -} - -fn adj_is_empty(adj: &AdjustmentValue) -> bool { - adj.lower() == 0.0 && adj.upper() == 0.0 -} - -fn set_fan_info(adjustment: &AdjustmentValue, info: Option) { - match info { - Some(info) => { - if let Some((min, max)) = info.allowed_range { - adjustment.set_lower(min as f64); - adjustment.set_upper(max as f64); - } else { - adjustment.set_lower(0.0); - adjustment.set_upper(info.current as f64); - } - - adjustment.set_initial_value(info.current as f64); - } - None => { - adjustment.set_lower(0.0); - adjustment.set_upper(0.0); - adjustment.set_initial_value(0.0); + if self.zero_rpm_available { + pmfw_config.zero_rpm = Some(self.zero_rpm.value()); } } + + fn has_pmfw_options(&self) -> bool { + self.zero_rpm_available + || !self.pmfw_rows.is_empty() + || !self.zero_rpm_temperature.is_empty() + } +} + +fn fan_info_init(title: String, info: Option) -> Option { + let info = info?; + let (min, max) = info.allowed_range.unwrap_or((0, info.current)); + (min != 0 || max != 0).then(|| AdjustmentRowInit { + title: glib::markup_escape_text(&title).into(), + value: info.current as f64, + lower: min as f64, + upper: max as f64, + page_increment: 5.0, + ..Default::default() + }) } diff --git a/lact-gui/src/app/pages/thermals_page/fan_curve_frame.rs b/lact-gui/src/app/pages/thermals_page/fan_curve_frame.rs index af295b04..837334d2 100644 --- a/lact-gui/src/app/pages/thermals_page/fan_curve_frame.rs +++ b/lact-gui/src/app/pages/thermals_page/fan_curve_frame.rs @@ -1,22 +1,16 @@ -use super::{FanSettingRow, PmfwOptions, adj_is_empty}; use crate::{ APP_BROKER, I18N, app::{ - components::adjustment_value::AdjustmentValue, graphs_window::plot::PlotColorScheme, + components::{ + adjustment_card::AdjustmentCard, + adjustment_row::{AdjustmentRow, AdjustmentRowInit, AdjustmentRowMsg}, + }, + graphs_window::plot::PlotColorScheme, msg::AppMsg, }, }; -use gtk::{ - gdk, - gio::prelude::ListModelExt, - glib::{ - self, SignalHandlerId, - object::{Cast, ObjectExt}, - }, - prelude::{ - AdjustmentExt, BoxExt, ButtonExt, DrawingAreaExtManual, OrientableExt, RangeExt, WidgetExt, - }, -}; +use adw::prelude::*; +use gtk::{gdk, glib}; use i18n_embed_fl::fl; use indexmap::IndexMap; use lact_schema::{FanCurveMap, TemperatureEntry, default_fan_curve}; @@ -32,14 +26,13 @@ use plotters::{ }; use plotters_cairo::CairoBackend; use relm4::{ - ComponentParts, ComponentSender, RelmObjectExt, RelmWidgetExt, - binding::{ConnectBinding, U32Binding}, + ComponentParts, ComponentSender, RelmObjectExt, RelmWidgetExt, WidgetTemplate, + binding::U32Binding, factory::FactoryHashMap, }; use std::{ cell::{Cell, RefCell}, ops::RangeInclusive, rc::Rc, - sync::atomic::{AtomicBool, Ordering}, }; pub const DEFAULT_TEMP_RANGE: RangeInclusive = 20.0..=115.0; @@ -51,33 +44,39 @@ const DEFAULT_SPINDOWN_DELAY_MS: u64 = 5000; const TEMPERATURE_DRAG_MARGIN: f32 = 4.0; const PERCENTAGE_DRAG_MARGIN: f32 = 0.04; -#[derive(Clone)] pub(super) struct FanCurveFrame { - pmfw_options: PmfwOptions, + adjustments: FactoryHashMap>, + label_size_group: gtk::SizeGroup, + input_size_group: gtk::SizeGroup, /// PMFW fan control on AMD RDNA3+ with fixed length - hw_based_fan_curve: Rc, - - data: Rc>>, - speed_range: Rc>>, - temperature_range: Rc>>, + hw_based_fan_curve: bool, + chart: Rc, temp_keys: gtk::StringList, current_temp_key: U32Binding, +} - spindown_delay_adj: AdjustmentValue, - change_threshold_adj: AdjustmentValue, - auto_threshold_adj: AdjustmentValue, - change_signals: Rc<[(glib::Object, SignalHandlerId)]>, +struct ChartState { + data: RefCell>, + speed_range: RefCell>, + temperature_range: RefCell>, - is_dragging: Rc, + is_dragging: Cell, /// Index of the point currently being dragged - drag_point: Rc>>, + drag_point: Cell>, /// Where the point was last moved to - drag_coord: Rc>>, + drag_coord: Cell>, /// Index of the point currently being hovered on - hover_point: Rc>>, + hover_point: Cell>, /// Where the point was last hovered on - hover_coord: Rc>>, + hover_coord: Cell>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(super) enum CurveSetting { + SpindownDelay, + ChangeThreshold, + AutoThreshold, } #[derive(Debug)] @@ -109,12 +108,13 @@ pub(super) struct CurveSetupMsg { #[relm4::component(pub)] impl relm4::Component for FanCurveFrame { - type Init = PmfwOptions; + type Init = (); type Input = FanCurveFrameMsg; type Output = (); type CommandOutput = (); view! { + #[root] gtk::Box { set_orientation: gtk::Orientation::Vertical, set_spacing: 5, @@ -123,8 +123,8 @@ impl relm4::Component for FanCurveFrame { gtk::DrawingArea { set_expand: true, set_height_request: 350, - set_draw_func[model = model.clone()] => move |_, ctx, width, height| { - model.draw_chart(ctx, width, height, PlotColorScheme::current()); + set_draw_func[chart = model.chart.clone()] => move |_, ctx, width, height| { + chart.draw_chart(ctx, width, height, PlotColorScheme::current()); }, add_controller = gtk::GestureClick { @@ -144,7 +144,7 @@ impl relm4::Component for FanCurveFrame { }, #[watch] - set_cursor: if model.is_dragging.load(Ordering::Relaxed) { + set_cursor: if model.chart.is_dragging.get() { gdk::Cursor::from_name("move", None) } else { None @@ -160,14 +160,14 @@ impl relm4::Component for FanCurveFrame { set_icon_name: "list-add-symbolic", connect_clicked => FanCurveFrameMsg::AddPoint, #[watch] - set_visible: !model.hw_based_fan_curve.load(Ordering::SeqCst), + set_visible: !model.hw_based_fan_curve, }, gtk::Button { set_icon_name: "list-remove-symbolic", connect_clicked => FanCurveFrameMsg::RemovePoint, #[watch] - set_visible: !model.hw_based_fan_curve.load(Ordering::SeqCst), + set_visible: !model.hw_based_fan_curve, }, gtk::Button { @@ -178,14 +178,15 @@ impl relm4::Component for FanCurveFrame { gtk::Box { set_orientation: gtk::Orientation::Horizontal, + add_css_class: "fan-control-option", set_spacing: 5, #[watch] set_visible: model.temp_keys_available(), gtk::Label { set_label: &fl!(I18N, "temperature-sensor"), + set_size_group: &model.label_size_group, set_xalign: 0.0, - set_size_group: &label_size_group, }, #[name = "temp_key_dropdown"] @@ -197,184 +198,58 @@ impl relm4::Component for FanCurveFrame { }, }, - #[template] - FanSettingRow { + model.adjustments.widget().clone() -> gtk::ListBox { #[watch] - set_visible: !model.hw_based_fan_curve.load(Ordering::SeqCst), - - #[template_child] - label { - set_label: &fl!(I18N, "spindown-delay"), - set_tooltip: &fl!(I18N, "spindown-delay-tooltip"), - set_size_group: &label_size_group, - }, - - #[template_child] - scale { - set_adjustment: &model.spindown_delay_adj, - }, - - #[template_child] - spinbutton { - set_adjustment: &model.spindown_delay_adj, - set_size_group: &spin_size_group, - }, + set_visible: !model.adjustments.is_empty(), }, + }, - #[template] - FanSettingRow { - #[watch] - set_visible: !model.hw_based_fan_curve.load(Ordering::SeqCst), - - #[template_child] - label { - set_label: &fl!(I18N, "speed-change-threshold"), - set_size_group: &label_size_group, - }, - - #[template_child] - scale { - set_adjustment: &model.change_threshold_adj, - }, - - #[template_child] - spinbutton { - set_adjustment: &model.change_threshold_adj, - set_size_group: &spin_size_group, - }, - }, - - #[template] - FanSettingRow { - #[watch] - set_visible: !adj_is_empty(&model.auto_threshold_adj), - - #[template_child] - label { - set_label: &fl!(I18N, "automatic-mode-threshold"), - set_tooltip: &fl!(I18N, "automatic-mode-threshold-tooltip"), - set_size_group: &label_size_group, - }, - - #[template_child] - scale { - set_adjustment: &model.auto_threshold_adj, - }, - - #[template_child] - spinbutton { - set_adjustment: &model.auto_threshold_adj, - set_size_group: &spin_size_group, - }, - }, - - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_spacing: 5, - #[watch] - set_visible: model.pmfw_options.zero_rpm_available.get(), - - gtk::Label { - set_label: &fl!(I18N, "zero-rpm"), - set_xalign: 0.0, - set_size_group: &label_size_group, - }, - - gtk::Switch { - bind: &model.pmfw_options.zero_rpm, - set_hexpand: true, - set_halign: gtk::Align::End, - }, - }, - - #[template] - FanSettingRow { - #[watch] - set_visible: !adj_is_empty(&model.pmfw_options.zero_rpm_temperature), - - #[template_child] - label { - set_label: &fl!(I18N, "zero-rpm-stop-temp"), - set_size_group: &label_size_group, - }, - - #[template_child] - scale { - set_adjustment: &model.pmfw_options.zero_rpm_temperature, - }, - - #[template_child] - spinbutton { - set_adjustment: &model.pmfw_options.zero_rpm_temperature, - set_size_group: &spin_size_group, - }, - }, - } + #[local_ref] + current_temp_key -> U32Binding { + connect_value_notify => move |_| { + APP_BROKER.send(AppMsg::SettingsChanged); + } @ temp_key_change_signal, + }, } fn post_view() { - if self.is_dragging.load(Ordering::SeqCst) { + if self.chart.is_dragging.get() { drawing_area.queue_draw(); } } fn init( - pmfw_options: Self::Init, + _init: Self::Init, root: Self::Root, sender: ComponentSender, ) -> ComponentParts { - let spindown_delay_adj = - AdjustmentValue::new(DEFAULT_SPINDOWN_DELAY_MS as f64, 0.0, 30_000.0, 10.0, 10.0); - let change_threshold_adj = - AdjustmentValue::new(DEFAULT_CHANGE_THRESHOLD as f64, 0.0, 10.0, 1.0, 1.0); - let auto_threshold_adj = AdjustmentValue::new(0.0, 0.0, 0.0, 1.0, 5.0); let temp_keys = gtk::StringList::default(); let current_temp_key = U32Binding::new(0u32); - let mut model = Self { - pmfw_options, - hw_based_fan_curve: Rc::new(AtomicBool::new(false)), - is_dragging: Rc::new(AtomicBool::new(false)), - speed_range: Rc::new(RefCell::new(DEFAULT_SPEED_RANGE)), - temperature_range: Rc::new(RefCell::new(DEFAULT_TEMP_RANGE)), - spindown_delay_adj, - change_threshold_adj, - auto_threshold_adj, + let model = Self { + adjustments: FactoryHashMap::builder() + .launch(AdjustmentCard::init(()).content.clone()) + .forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged), + label_size_group: gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal), + input_size_group: gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal), + hw_based_fan_curve: false, + chart: Rc::new(ChartState { + data: RefCell::default(), + speed_range: RefCell::new(DEFAULT_SPEED_RANGE), + temperature_range: RefCell::new(DEFAULT_TEMP_RANGE), + is_dragging: Cell::new(false), + drag_coord: Cell::default(), + drag_point: Cell::default(), + hover_coord: Cell::default(), + hover_point: Cell::default(), + }), temp_keys, current_temp_key, - change_signals: Rc::default(), - data: Rc::default(), - drag_coord: Rc::default(), - drag_point: Rc::default(), - hover_coord: Rc::default(), - hover_point: Rc::default(), }; - let label_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal); - let spin_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal); - + let current_temp_key = &model.current_temp_key; let widgets = view_output!(); - model.change_signals = [ - &model.spindown_delay_adj, - &model.change_threshold_adj, - &model.auto_threshold_adj, - ] - .into_iter() - .map(|adj| { - let signal = adj.connect_value_changed(|_| { - APP_BROKER.send(AppMsg::SettingsChanged); - }); - (adj.clone().upcast(), signal) - }) - .chain([( - model.current_temp_key.clone().upcast(), - model.current_temp_key.connect_value_notify(|_| { - APP_BROKER.send(AppMsg::SettingsChanged); - }), - )]) - .collect(); - ComponentParts { model, widgets } } @@ -387,17 +262,15 @@ impl relm4::Component for FanCurveFrame { ) { match msg { FanCurveFrameMsg::Curve(msg) => { - *self.data.borrow_mut() = + *self.chart.data.borrow_mut() = normalize_fan_curve(msg.curve, &msg.temperature_range, &msg.speed_range) .collect(); - *self.speed_range.borrow_mut() = msg.speed_range; - *self.temperature_range.borrow_mut() = msg.temperature_range.clone(); - self.hw_based_fan_curve - .store(msg.hw_based, Ordering::SeqCst); + *self.chart.speed_range.borrow_mut() = msg.speed_range; + *self.chart.temperature_range.borrow_mut() = msg.temperature_range.clone(); + self.hw_based_fan_curve = msg.hw_based; - for (adj, signal) in self.change_signals.iter() { - adj.block_signal(signal); - } + self.current_temp_key + .block_signal(&widgets.temp_key_change_signal); let mut temp_keys = msg .current_temperatures @@ -424,51 +297,84 @@ impl relm4::Component for FanCurveFrame { widgets.temp_key_dropdown.set_selected(idx as u32); } - self.spindown_delay_adj.set_initial_value( - msg.spindown_delay.unwrap_or(DEFAULT_SPINDOWN_DELAY_MS) as f64, - ); - self.change_threshold_adj.set_initial_value( - msg.change_threshold.unwrap_or(DEFAULT_CHANGE_THRESHOLD) as f64, - ); - - if msg.auto_threshold_supported { - self.auto_threshold_adj.set_lower(0.0); - self.auto_threshold_adj - .set_upper(*msg.temperature_range.end() as f64); - self.auto_threshold_adj.set_initial_value( - msg.auto_threshold.unwrap_or(DEFAULT_AUTO_THRESHOLD) as f64, + self.current_temp_key + .unblock_signal(&widgets.temp_key_change_signal); + self.adjustments.clear(); + if !msg.hw_based { + self.adjustments.insert( + CurveSetting::SpindownDelay, + AdjustmentRowInit { + title: glib::markup_escape_text(&fl!(I18N, "spindown-delay")).into(), + value: msg.spindown_delay.unwrap_or(DEFAULT_SPINDOWN_DELAY_MS) as f64, + upper: 30_000.0, + step_increment: 10.0, + page_increment: 10.0, + title_tooltip: fl!(I18N, "spindown-delay-tooltip"), + ..Default::default() + }, + ); + self.adjustments.insert( + CurveSetting::ChangeThreshold, + AdjustmentRowInit { + title: glib::markup_escape_text(&fl!(I18N, "speed-change-threshold")) + .into(), + value: msg.change_threshold.unwrap_or(DEFAULT_CHANGE_THRESHOLD) as f64, + upper: 10.0, + page_increment: 1.0, + ..Default::default() + }, + ); + } + if msg.auto_threshold_supported && *msg.temperature_range.end() != 0.0 { + self.adjustments.insert( + CurveSetting::AutoThreshold, + AdjustmentRowInit { + title: glib::markup_escape_text(&fl!(I18N, "automatic-mode-threshold")) + .into(), + value: msg.auto_threshold.unwrap_or(DEFAULT_AUTO_THRESHOLD) as f64, + upper: *msg.temperature_range.end() as f64, + title_tooltip: fl!(I18N, "automatic-mode-threshold-tooltip"), + page_increment: 5.0, + ..Default::default() + }, ); - } else { - self.auto_threshold_adj.set_lower(0.0); - self.auto_threshold_adj.set_upper(0.0); } - for (adj, signal) in self.change_signals.iter() { - adj.unblock_signal(signal); + let lower_label_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal); + let upper_label_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal); + for setting in self.adjustments.keys() { + self.adjustments.send( + setting, + AdjustmentRowMsg::AddSizeGroup { + label_group: self.label_size_group.clone(), + input_group: self.input_size_group.clone(), + lower_label_group: lower_label_group.clone(), + upper_label_group: upper_label_group.clone(), + }, + ); } - widgets.drawing_area.queue_draw(); } FanCurveFrameMsg::DragStart => { - self.is_dragging.store(true, Ordering::SeqCst); + self.chart.is_dragging.set(true); } FanCurveFrameMsg::DragUpdate(x, y) => { - self.hover_coord.set(Some((x, y))); + self.chart.hover_coord.set(Some((x, y))); - if self.is_dragging.load(Ordering::SeqCst) { - self.drag_coord.set(Some((x, y))); + if self.chart.is_dragging.get() { + self.chart.drag_coord.set(Some((x, y))); } widgets.drawing_area.queue_draw(); } FanCurveFrameMsg::DragEnd => { - self.drag_coord.take(); - self.drag_point.take(); - self.is_dragging.store(false, Ordering::SeqCst); + self.chart.drag_coord.take(); + self.chart.drag_point.take(); + self.chart.is_dragging.set(false); } FanCurveFrameMsg::AddPoint => { - let temp_range = self.temperature_range.borrow(); - let speed_range = self.speed_range.borrow(); + let temp_range = self.chart.temperature_range.borrow(); + let speed_range = self.chart.speed_range.borrow(); self.edit_curve( |curve| { @@ -490,17 +396,22 @@ impl relm4::Component for FanCurveFrame { |curve| { *curve = normalize_fan_curve( default_fan_curve(), - &self.temperature_range.borrow(), - &self.speed_range.borrow(), + &self.chart.temperature_range.borrow(), + &self.chart.speed_range.borrow(), ) .collect(); }, widgets, ); - self.spindown_delay_adj - .set_value(DEFAULT_SPINDOWN_DELAY_MS as f64); - self.change_threshold_adj - .set_value(DEFAULT_CHANGE_THRESHOLD as f64); + for (setting, value) in [ + (CurveSetting::SpindownDelay, DEFAULT_SPINDOWN_DELAY_MS), + (CurveSetting::ChangeThreshold, DEFAULT_CHANGE_THRESHOLD), + ] { + if self.adjustments.get(&setting).is_some() { + self.adjustments + .send(&setting, AdjustmentRowMsg::SetValue(value as f64)); + } + } } } self.update_view(widgets, sender); @@ -509,15 +420,21 @@ impl relm4::Component for FanCurveFrame { impl FanCurveFrame { pub fn get_curve(&self) -> FanCurveMap { - self.data.borrow().iter().copied().collect() + self.chart.data.borrow().iter().copied().collect() } pub fn spindown_delay(&self) -> u64 { - self.spindown_delay_adj.value() as u64 + self.adjustments + .get(&CurveSetting::SpindownDelay) + .map(|row| row.get_value() as u64) + .unwrap_or(DEFAULT_SPINDOWN_DELAY_MS) } pub fn change_threshold(&self) -> u64 { - self.change_threshold_adj.value() as u64 + self.adjustments + .get(&CurveSetting::ChangeThreshold) + .map(|row| row.get_value() as u64) + .unwrap_or(DEFAULT_CHANGE_THRESHOLD) } pub fn temperature_key(&self) -> Option { @@ -531,24 +448,27 @@ impl FanCurveFrame { } pub fn auto_threshold(&self) -> Option { - self.auto_threshold_adj - .get_changed_value(false) - .map(|val| val as u64) + self.adjustments + .get(&CurveSetting::AutoThreshold) + .and_then(|row| row.get_changed_value()) + .map(|value| value as u64) } fn temp_keys_available(&self) -> bool { - !self.hw_based_fan_curve.load(Ordering::SeqCst) + !self.hw_based_fan_curve && self.temp_keys.n_items() > 1 - && (self.auto_threshold_adj.upper() == 0.0) // Disable key selection on nvidia + && self.adjustments.get(&CurveSetting::AutoThreshold).is_none() // Disable key selection on nvidia } fn edit_curve(&self, f: impl FnOnce(&mut Vec<(i32, f32)>), widgets: &FanCurveFrameWidgets) { - f(&mut self.data.borrow_mut()); + f(&mut self.chart.data.borrow_mut()); widgets.drawing_area.queue_draw(); APP_BROKER.send(AppMsg::SettingsChanged); } +} +impl ChartState { fn draw_chart(&self, ctx: &cairo::Context, width: i32, height: i32, colors: PlotColorScheme) { let cairo_backend = CairoBackend::new(ctx, (width as u32, height as u32)).unwrap();