diff --git a/lact-gui/src/app/pages/oc_adjustment/mod.rs b/lact-gui/src/app/pages/oc_adjustment/mod.rs index a54fe3f..3bc6403 100644 --- a/lact-gui/src/app/pages/oc_adjustment/mod.rs +++ b/lact-gui/src/app/pages/oc_adjustment/mod.rs @@ -15,6 +15,12 @@ glib::wrapper! { @implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; } +impl Default for OcAdjustment { + fn default() -> Self { + Object::builder().build() + } +} + impl OcAdjustment { pub fn new( value: f64, @@ -24,7 +30,7 @@ impl OcAdjustment { page_increment: f64, page_size: f64, ) -> Self { - let oc_adjustment: Self = Object::builder().build(); + let oc_adjustment = Self::default(); let adjustment = oc_adjustment.imp().obj(); adjustment.set_lower(lower); diff --git a/lact-gui/src/app/pages/oc_page.rs b/lact-gui/src/app/pages/oc_page.rs index 4284179..54d4ae6 100644 --- a/lact-gui/src/app/pages/oc_page.rs +++ b/lact-gui/src/app/pages/oc_page.rs @@ -13,16 +13,15 @@ use amdgpu_sysfs::gpu_handle::{ use clocks_frame::ClocksFrame; use gpu_stats_section::GpuStatsSection; use gtk::{ - glib::object::ObjectExt, pango, prelude::{BoxExt, ButtonExt, FrameExt, OrientableExt, WidgetExt}, }; use lact_daemon::MODULE_CONF_PATH; use lact_schema::{request::SetClocksCommand, ClocksTable, PowerStates, SystemInfo}; use performance_frame::PerformanceFrame; -use power_cap_section::PowerCapSection; +use power_cap_section::{PowerCapMsg, PowerCapSection}; use power_states::power_states_frame::PowerStatesFrame; -use relm4::{ComponentParts, ComponentSender, RelmWidgetExt}; +use relm4::{ComponentController, ComponentParts, ComponentSender, RelmWidgetExt}; use std::{cell::Cell, collections::HashMap, rc::Rc}; use tracing::warn; @@ -32,7 +31,7 @@ You can still change basic settings, but the more advanced clocks and voltage co pub struct OcPage { stats_section: GpuStatsSection, pub performance_frame: PerformanceFrame, - power_cap_section: PowerCapSection, + power_cap_section: relm4::Controller, power_states_frame: PowerStatesFrame, clocks_frame: ClocksFrame, // TODO: refactor this out when child components use senders @@ -96,11 +95,7 @@ impl relm4::Component for OcPage { model.stats_section.clone(), - model.power_cap_section.clone() { - connect_current_value_notify[sender] => move |_| { - sender.output(AppMsg::SettingsChanged).unwrap(); - } @power_cap_notify, - }, + model.power_cap_section.widget(), model.performance_frame.container.clone(), @@ -116,10 +111,12 @@ impl relm4::Component for OcPage { root: Self::Root, sender: ComponentSender, ) -> ComponentParts { + let power_cap_section = PowerCapSection::builder().launch(()).detach(); + let model = Self { stats_section: GpuStatsSection::new(), performance_frame: PerformanceFrame::new(), - power_cap_section: PowerCapSection::new(), + power_cap_section, power_states_frame: PowerStatesFrame::new(), clocks_frame: ClocksFrame::new(), signals_blocked: Rc::new(Cell::new(false)), @@ -143,27 +140,19 @@ impl relm4::Component for OcPage { ) { self.signals_blocked.set(true); match msg { - OcPageMsg::Update { update, initial } => match update { + OcPageMsg::Update { update, initial } => match &update { PageUpdate::Stats(stats) => { - self.stats_section.set_stats(&stats); - self.power_states_frame.set_stats(&stats); + self.stats_section.set_stats(stats); + self.power_states_frame.set_stats(stats); if initial { self.power_cap_section - .block_signal(&widgets.power_cap_notify); + .emit(PowerCapMsg::Update(update.clone())); - self.power_cap_section - .set_max_value(stats.power.cap_max.unwrap_or_default()); - self.power_cap_section - .set_min_value(stats.power.cap_min.unwrap_or_default()); - self.power_cap_section - .set_default_value(stats.power.cap_default.unwrap_or_default()); - - if let Some(current_cap) = stats.power.cap_current { - self.power_cap_section.set_initial_value(current_cap); - self.power_cap_section.set_visible(true); + if stats.power.cap_current.is_some() { + self.power_cap_section.widget().set_visible(true); } else { - self.power_cap_section.set_visible(false); + self.power_cap_section.widget().set_visible(false); } match stats.performance_level { @@ -173,9 +162,6 @@ impl relm4::Component for OcPage { } None => self.performance_frame.hide(), } - - self.power_cap_section - .unblock_signal(&widgets.power_cap_notify); } } PageUpdate::Info(info) => { @@ -216,9 +202,10 @@ impl relm4::Component for OcPage { self.signals_blocked.set(false); let signals_blocked = self.signals_blocked.clone(); + let signals_sender = sender.clone(); let f = move || { if !signals_blocked.get() { - sender + signals_sender .output(AppMsg::SettingsChanged) .expect("Channel closed") } @@ -226,6 +213,8 @@ impl relm4::Component for OcPage { self.performance_frame.connect_settings_changed(f.clone()); self.clocks_frame.connect_clocks_changed(f.clone()); self.power_states_frame.connect_values_changed(f); + + self.update_view(widgets, sender); } } @@ -240,7 +229,7 @@ impl OcPage { } pub fn get_power_cap(&self) -> Option { - self.power_cap_section.get_user_cap() + self.power_cap_section.model().get_user_cap() } pub fn get_clocks_commands(&self) -> Vec { 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 index 5690c2d..08db974 100644 --- a/lact-gui/src/app/pages/oc_page/power_cap_section.rs +++ b/lact-gui/src/app/pages/oc_page/power_cap_section.rs @@ -1,109 +1,136 @@ -use crate::app::page_section::PageSection; -use gtk::glib::{self, subclass::types::ObjectSubclassIsExt, Object}; +use crate::{ + app::{ + msg::AppMsg, + page_section::PageSection, + pages::{oc_adjustment::OcAdjustment, PageUpdate}, + }, + APP_BROKER, +}; +use gtk::{ + glib::object::ObjectExt, + prelude::{AdjustmentExt, BoxExt, ButtonExt, OrientableExt, RangeExt, ScaleExt, WidgetExt}, +}; +use lact_schema::PowerStats; +use relm4::{ComponentParts, ComponentSender, RelmWidgetExt}; +use std::fmt::Write; -glib::wrapper! { - pub struct PowerCapSection(ObjectSubclass) - @extends PageSection, gtk::Box, gtk::Widget, - @implements gtk::Orientable, gtk::Accessible, gtk::Buildable; +#[derive(Default)] +pub struct PowerCapSection { + power: PowerStats, + adjustment: OcAdjustment, + 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("Power usage limit") { + append = >k::Box { + set_orientation: gtk::Orientation::Horizontal, + + 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, + }, + + gtk::Button { + set_label: "Default", + connect_clicked => PowerCapMsg::Reset, + }, + } + }, + + #[local_ref] + adjustment -> OcAdjustment { + 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; + + 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, + "{}/{} W", + self.adjustment.value(), + self.adjustment.upper() + ) + .unwrap(); + } + PowerCapMsg::Reset => { + self.adjustment + .set_value(self.power.cap_default.unwrap_or_default()); + } + } + + self.update_view(widgets, sender); + } } impl PowerCapSection { - pub fn new() -> Self { - Object::builder().build() - } - pub fn get_user_cap(&self) -> Option { - let imp = self.imp(); - imp.adjustment.get_changed_value(true) - } - - pub fn set_initial_value(&self, value: f64) { - self.imp().adjustment.set_initial_value(value); + self.adjustment.get_changed_value(true) } } - -impl Default for PowerCapSection { - fn default() -> Self { - Self::new() - } -} - -mod imp { - use crate::app::{page_section::PageSection, pages::oc_adjustment::OcAdjustment}; - use gtk::{ - glib::{self, clone, subclass::InitializingObject, types::StaticTypeExt, Properties}, - prelude::{ButtonExt, ObjectExt}, - subclass::{ - prelude::*, - widget::{CompositeTemplateClass, WidgetImpl}, - }, - Button, CompositeTemplate, - }; - use std::cell::RefCell; - - #[derive(CompositeTemplate, Default, Properties)] - #[properties(wrapper_type = super::PowerCapSection)] - #[template(file = "ui/oc_page/power_cap_section.blp")] - pub struct PowerCapSection { - #[property(get, set)] - pub current_value: RefCell, - #[property(get, set)] - pub max_value: RefCell, - #[property(get, set)] - pub min_value: RefCell, - #[property(get, set)] - pub default_value: RefCell, - #[property(get, set)] - pub value_text: RefCell, - - #[template_child] - pub adjustment: TemplateChild, - #[template_child] - pub reset_button: TemplateChild