diff --git a/Cargo.lock b/Cargo.lock index 74bbdad..2a9e554 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,9 +53,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "amdgpu-sysfs" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b277cf650bff281b49b12f4a2a4a004485e0b3ecfd34961c5f9b258800ea09" +checksum = "cd30ccf0f663fbe9465c7819c0f52cfcb9bf890c7445192f23ff381fad0b3204" dependencies = [ "enum_dispatch", "serde", diff --git a/Cargo.toml b/Cargo.toml index 1cb6b66..5a53ce0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ members = [ ] [workspace.dependencies] -amdgpu-sysfs = { version = "0.15.0", features = ["serde"] } +amdgpu-sysfs = { version = "0.16.0", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } serde_with = { version = "3.5.0", default-features = false, features = [ "macros", diff --git a/lact-gui/src/app/root_stack/oc_page/mod.rs b/lact-gui/src/app/root_stack/oc_page/mod.rs index eb1b78e..c78d9dc 100644 --- a/lact-gui/src/app/root_stack/oc_page/mod.rs +++ b/lact-gui/src/app/root_stack/oc_page/mod.rs @@ -2,7 +2,7 @@ mod clocks_frame; mod gpu_stats_section; mod performance_frame; mod power_cap_section; -// mod power_cap_frame; +mod power_profile; mod power_states; use self::power_cap_section::PowerCapSection; diff --git a/lact-gui/src/app/root_stack/oc_page/performance_frame.rs b/lact-gui/src/app/root_stack/oc_page/performance_frame.rs index 12b6851..3ba870d 100644 --- a/lact-gui/src/app/root_stack/oc_page/performance_frame.rs +++ b/lact-gui/src/app/root_stack/oc_page/performance_frame.rs @@ -5,15 +5,19 @@ use gtk::prelude::*; use gtk::*; use std::{cell::RefCell, rc::Rc, str::FromStr}; +use super::power_profile::power_profile_component_grid::PowerProfileComponentGrid; + #[derive(Clone)] pub struct PerformanceFrame { pub container: PageSection, level_drop_down: DropDown, - mode_drop_down: DropDown, + modes_listbox: ListBox, + mode_menu_button: MenuButton, description_label: Label, manual_info_button: MenuButton, mode_box: Box, modes_table: Rc>>, + power_mode_info_notebook: Notebook, } impl PerformanceFrame { @@ -41,11 +45,32 @@ impl PerformanceFrame { let mode_box = Box::new(Orientation::Horizontal, 10); - let mode_drop_down = DropDown::builder() + let mode_menu_button = MenuButton::builder() .sensitive(false) .halign(Align::End) + .always_show_arrow(false) .build(); + let modes_listbox = ListBox::builder() + .selection_mode(SelectionMode::Single) + .build(); + + let modes_popover_content = gtk::Box::builder() + .orientation(Orientation::Horizontal) + .spacing(10) + .margin_start(5) + .margin_end(5) + .margin_top(5) + .margin_bottom(5) + .build(); + modes_popover_content.append(&modes_listbox); + + let power_mode_info_notebook = Notebook::new(); + modes_popover_content.append(&power_mode_info_notebook); + + let modes_popover = Popover::builder().child(&modes_popover_content).build(); + mode_menu_button.set_popover(Some(&modes_popover)); + let unavailable_label = Label::new(Some( "Performance level has to be set to \"manual\" to use power states and modes", )); @@ -60,18 +85,20 @@ impl PerformanceFrame { let mode_title_label = Label::new(Some("Power level mode:")); mode_box.append(&mode_title_label); mode_box.append(&manual_info_button); - mode_box.append(&mode_drop_down); + mode_box.append(&mode_menu_button); container.append(&mode_box); let frame = Self { container, level_drop_down, - mode_drop_down, + mode_menu_button, description_label, manual_info_button, + modes_listbox, mode_box, modes_table: Rc::new(RefCell::new(None)), + power_mode_info_notebook, }; frame.level_drop_down.connect_selected_notify(clone!( @@ -82,12 +109,10 @@ impl PerformanceFrame { } )); - frame.mode_drop_down.connect_selected_notify(clone!( + frame.modes_listbox.connect_row_selected(clone!( #[strong] frame, - move |_| { - frame.update_from_selection(); - } + move |_, _| frame.update_from_selection() )); frame @@ -107,25 +132,36 @@ impl PerformanceFrame { pub fn set_power_profile_modes(&self, table: Option) { self.mode_box.set_visible(table.is_some()); + while let Some(row) = self.modes_listbox.row_at_index(0) { + self.modes_listbox.remove(&row); + } + match &table { Some(table) => { - let model: StringList = table.modes.values().cloned().collect(); - let active_pos = table - .modes - .keys() - .position(|key| *key == table.active) - .expect("No active mode") as u32; + for profile in table.modes.values() { + let profile_label = Label::builder() + .label(&profile.name) + .margin_start(5) + .margin_end(5) + .build(); + self.modes_listbox.append(&profile_label); + } - self.mode_drop_down.set_model(Some(&model)); - self.mode_drop_down.set_selected(active_pos); + let active_row = self + .modes_listbox + .row_at_index(table.active as i32) + .unwrap(); + self.modes_listbox.select_row(Some(&active_row)); - self.mode_drop_down.show(); + self.mode_menu_button.show(); } None => { - self.mode_drop_down.hide(); + self.mode_menu_button.hide(); } } self.modes_table.replace(table); + + self.update_from_selection(); } pub fn connect_settings_changed(&self, f: F) { @@ -134,7 +170,21 @@ impl PerformanceFrame { f, move |_| f() )); - self.mode_drop_down.connect_selected_notify(move |_| f()); + self.modes_listbox.connect_row_selected(clone!( + #[strong(rename_to = modes_table)] + self.modes_table, + move |_, row| { + let modes_table = modes_table.borrow(); + + if let Some(row) = row { + if let Some(table) = modes_table.as_ref() { + if row.index() != table.active as i32 { + f(); + } + } + } + } + )); } pub fn get_selected_performance_level(&self) -> PerformanceLevel { @@ -148,21 +198,18 @@ impl PerformanceFrame { } pub fn get_selected_power_profile_mode(&self) -> Option { - if self.mode_drop_down.is_sensitive() { - self.modes_table.borrow().as_ref().map(|table| { - let selected_index = table - .modes - .keys() - .nth(self.mode_drop_down.selected() as usize) - .expect("Selected mode out of range"); - *selected_index - }) + if self.mode_menu_button.is_sensitive() { + self.modes_listbox + .selected_row() + .map(|row| row.index() as u16) } else { None } } fn update_from_selection(&self) { + self.power_mode_info_notebook.set_visible(false); + let mut enable_mode_control = false; let text = match self.level_drop_down.selected() { @@ -176,10 +223,51 @@ impl PerformanceFrame { _ => unreachable!(), }; self.description_label.set_text(text); - self.mode_drop_down.set_sensitive(enable_mode_control); self.manual_info_button.set_visible(!enable_mode_control); - self.mode_drop_down.set_hexpand(enable_mode_control); + + self.mode_menu_button.set_sensitive(enable_mode_control); + self.mode_menu_button.set_hexpand(enable_mode_control); + + let modes_table = self.modes_table.borrow(); + if let Some(table) = modes_table.as_ref() { + if let Some(row) = self.modes_listbox.selected_row() { + if let Some(profile) = table.modes.get(&(row.index() as u16)) { + self.mode_menu_button.set_label(&profile.name); + + self.power_mode_info_notebook.set_visible(true); + + // Save current page to be restored after being refilled + let current_page = self.power_mode_info_notebook.current_page(); + // Remove pages + while self.power_mode_info_notebook.n_pages() != 0 { + self.power_mode_info_notebook.remove_page(None); + } + + for component in &profile.components { + let values_grid = PowerProfileComponentGrid::new(); + values_grid.set_component(component, table); + + let title = component.clock_type.as_deref().unwrap_or("All"); + let title_label = Label::builder() + .label(title) + .margin_start(5) + .margin_end(5) + .build(); + self.power_mode_info_notebook + .append_page(&values_grid, Some(&title_label)); + } + + self.power_mode_info_notebook + .set_show_tabs(profile.components.len() > 1); + + // Restore selected page + if current_page.is_some() { + self.power_mode_info_notebook.set_current_page(current_page); + } + } + } + } } pub fn show(&self) { diff --git a/lact-gui/src/app/root_stack/oc_page/power_profile/mod.rs b/lact-gui/src/app/root_stack/oc_page/power_profile/mod.rs new file mode 100644 index 0000000..25ab35e --- /dev/null +++ b/lact-gui/src/app/root_stack/oc_page/power_profile/mod.rs @@ -0,0 +1 @@ +pub mod power_profile_component_grid; diff --git a/lact-gui/src/app/root_stack/oc_page/power_profile/power_profile_component_grid.rs b/lact-gui/src/app/root_stack/oc_page/power_profile/power_profile_component_grid.rs new file mode 100644 index 0000000..03c0d1a --- /dev/null +++ b/lact-gui/src/app/root_stack/oc_page/power_profile/power_profile_component_grid.rs @@ -0,0 +1,90 @@ +use amdgpu_sysfs::gpu_handle::power_profile_mode::{PowerProfileComponent, PowerProfileModesTable}; +use gtk::{ + glib::{self, Object}, + prelude::GridExt, + prelude::WidgetExt, + Label, +}; + +glib::wrapper! { + pub struct PowerProfileComponentGrid(ObjectSubclass) + @extends gtk::Grid, + @implements gtk::Accessible, gtk::Buildable, gtk::Widget; +} + +impl PowerProfileComponentGrid { + pub fn new() -> Self { + Object::builder() + .property("margin-start", 5) + .property("margin-end", 5) + .property("margin-top", 5) + .property("margin-bottom", 5) + .build() + } + + pub fn set_component(&self, component: &PowerProfileComponent, table: &PowerProfileModesTable) { + while let Some(child) = self.first_child() { + self.remove(&child); + } + + for (i, value) in component.values.iter().enumerate() { + let name = &table.value_names[i]; + + let name_label = Label::builder() + .label(&format!("{name}:")) + .hexpand(true) + .halign(gtk::Align::Start) + .build(); + + self.attach(&name_label, 0, i as i32, 1, 1); + + let mut value_label_builder = Label::builder().halign(gtk::Align::End); + value_label_builder = match value { + Some(value) => value_label_builder.label(value.to_string()), + None => value_label_builder.label("Not set"), + }; + self.attach(&value_label_builder.build(), 1, i as i32, 1, 1); + } + } +} + +impl Default for PowerProfileComponentGrid { + fn default() -> Self { + Self::new() + } +} + +mod imp { + use gtk::{ + glib::{self, subclass::InitializingObject}, + subclass::{ + prelude::*, + widget::{CompositeTemplateClass, WidgetImpl}, + }, + CompositeTemplate, + }; + + #[derive(CompositeTemplate, Default)] + #[template(file = "ui/oc_page/power_profile/power_profile_component_grid.blp")] + pub struct PowerProfileComponentGrid {} + + #[glib::object_subclass] + impl ObjectSubclass for PowerProfileComponentGrid { + const NAME: &'static str = "PowerProfileComponentGrid"; + type Type = super::PowerProfileComponentGrid; + type ParentType = gtk::Grid; + + fn class_init(class: &mut Self::Class) { + class.bind_template(); + } + + fn instance_init(obj: &InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for PowerProfileComponentGrid {} + + impl WidgetImpl for PowerProfileComponentGrid {} + impl GridImpl for PowerProfileComponentGrid {} +} diff --git a/lact-gui/ui/oc_page/power_profile/power_profile_component_grid.blp b/lact-gui/ui/oc_page/power_profile/power_profile_component_grid.blp new file mode 100644 index 0000000..936b364 --- /dev/null +++ b/lact-gui/ui/oc_page/power_profile/power_profile_component_grid.blp @@ -0,0 +1,6 @@ +using Gtk 4.0; + +template $PowerProfileComponentGrid: Grid { + row-spacing: 5; + column-spacing: 5; +}