refactor: make GpuStatsSection a relm component

This commit is contained in:
Ilya Zlobintsev
2025-02-01 21:23:13 +02:00
parent b4b0590039
commit 8c3a99c65c
4 changed files with 227 additions and 270 deletions

View File

@@ -23,6 +23,12 @@ impl InfoRow {
}
}
impl Default for InfoRow {
fn default() -> Self {
Object::builder().build()
}
}
mod imp {
use glib::Properties;
use gtk::{

View File

@@ -28,7 +28,7 @@ const OVERCLOCKING_DISABLED_TEXT: &str = "Overclocking support is not enabled! \
You can still change basic settings, but the more advanced clocks and voltage control will not be available.";
pub struct OcPage {
stats_section: GpuStatsSection,
stats_section: relm4::Controller<GpuStatsSection>,
pub performance_frame: PerformanceFrame,
power_cap_section: relm4::Controller<PowerCapSection>,
power_states_frame: PowerStatesFrame,
@@ -92,7 +92,7 @@ impl relm4::Component for OcPage {
},
},
model.stats_section.clone(),
model.stats_section.widget(),
model.power_cap_section.widget(),
model.performance_frame.container.clone(),
model.power_states_frame.clone(),
@@ -106,11 +106,12 @@ impl relm4::Component for OcPage {
root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let stats_section = GpuStatsSection::builder().launch(()).detach();
let power_cap_section = PowerCapSection::builder().launch(()).detach();
let clocks_frame = ClocksFrame::builder().launch(()).detach();
let model = Self {
stats_section: GpuStatsSection::new(),
stats_section,
performance_frame: PerformanceFrame::new(),
power_cap_section,
power_states_frame: PowerStatesFrame::new(),
@@ -132,40 +133,41 @@ impl relm4::Component for OcPage {
) {
self.signals_blocked.set(true);
match msg {
OcPageMsg::Update { update, initial } => match &update {
PageUpdate::Stats(stats) => {
self.stats_section.set_stats(stats);
self.power_states_frame.set_stats(stats);
OcPageMsg::Update { update, initial } => {
self.stats_section.emit(update.clone());
match &update {
PageUpdate::Stats(stats) => {
self.power_states_frame.set_stats(stats);
if initial {
self.power_cap_section
.emit(PowerCapMsg::Update(update.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);
}
match stats.performance_level {
Some(profile) => {
self.performance_frame.show();
self.performance_frame.set_active_level(profile);
if stats.power.cap_current.is_some() {
self.power_cap_section.widget().set_visible(true);
} else {
self.power_cap_section.widget().set_visible(false);
}
match stats.performance_level {
Some(profile) => {
self.performance_frame.show();
self.performance_frame.set_active_level(profile);
}
None => self.performance_frame.hide(),
}
None => self.performance_frame.hide(),
}
}
}
PageUpdate::Info(info) => {
let vram_clock_ratio = info.vram_clock_ratio();
PageUpdate::Info(info) => {
let vram_clock_ratio = info.vram_clock_ratio();
self.power_states_frame
.set_vram_clock_ratio(vram_clock_ratio);
self.stats_section.set_vram_clock_ratio(vram_clock_ratio);
self.clocks_frame
.emit(ClocksFrameMsg::VramRatio(vram_clock_ratio));
self.power_states_frame
.set_vram_clock_ratio(vram_clock_ratio);
self.clocks_frame
.emit(ClocksFrameMsg::VramRatio(vram_clock_ratio));
}
}
},
}
OcPageMsg::ClocksTable(table) => {
self.clocks_frame.emit(ClocksFrameMsg::Clocks(table));
}

View File

@@ -1,170 +1,211 @@
use crate::app::page_section::PageSection;
use gtk::glib::{self, Object};
use lact_client::schema::{DeviceStats, PowerStats};
use std::fmt::Write;
use crate::app::{info_row::InfoRow, page_section::PageSection, pages::PageUpdate};
use gtk::prelude::{ActionableExt, BoxExt, ButtonExt, OrientableExt, WidgetExt};
use lact_schema::{DeviceStats, PowerStats};
use relm4::{ComponentParts, ComponentSender};
use std::{fmt::Write, sync::Arc};
glib::wrapper! {
pub struct GpuStatsSection(ObjectSubclass<imp::GpuStatsSection>)
@extends PageSection, gtk::Box, gtk::Widget,
@implements gtk::Orientable, gtk::Accessible, gtk::Buildable;
pub struct GpuStatsSection {
stats: Arc<DeviceStats>,
vram_clock_ratio: f64,
}
impl GpuStatsSection {
pub fn new() -> Self {
Object::builder().property("vram_clock_ratio", 1.0).build()
}
#[relm4::component(pub)]
impl relm4::SimpleComponent for GpuStatsSection {
type Init = ();
type Input = PageUpdate;
type Output = ();
pub fn set_stats(&self, stats: &DeviceStats) {
let vram_usage =
if let (Some(used_vram), Some(total_vram)) = (stats.vram.used, stats.vram.total) {
used_vram as f64 / total_vram as f64
} else {
0.0
};
self.set_vram_usage(vram_usage);
self.set_vram_usage_text(format!(
"{}/{} MiB",
stats.vram.used.unwrap_or(0) / 1024 / 1024,
stats.vram.total.unwrap_or(0) / 1024 / 1024,
));
view! {
PageSection::new("Statistics") {
set_spacing: 10,
let clockspeed = stats.clockspeed;
self.set_core_clock(format_clockspeed(clockspeed.gpu_clockspeed, 1.0));
self.set_current_core_clock(format_current_gfxclk(clockspeed.current_gfxclk));
self.set_vram_clock(format_clockspeed(
clockspeed.vram_clockspeed,
self.vram_clock_ratio(),
));
append = &gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
set_spacing: 5,
let voltage = format!("{:.3} V", stats.voltage.gpu.unwrap_or(0) as f64 / 1000f64);
self.set_voltage(voltage);
gtk::Label {
set_label: "VRAM Usage:",
},
let temperature = if stats.temps.len() == 1 {
stats.temps.values().next().unwrap().current
} else {
stats
.temps
.get("junction")
.or_else(|| stats.temps.get("edge"))
.and_then(|temp| temp.current)
}
.unwrap_or(0.0);
self.set_temperature(format!("{temperature}°C"));
gtk::Overlay {
gtk::LevelBar {
set_hexpand: true,
set_orientation: gtk::Orientation::Horizontal,
#[watch]
set_value: model
.stats
.vram
.used
.zip(model.stats.vram.total)
.map(|(used, total)| used as f64 / total as f64)
.unwrap_or(0.0),
},
self.set_gpu_usage(format!("{}%", stats.busy_percent.unwrap_or(0)));
add_overlay = &gtk::Label {
#[watch]
set_label: &format!(
"{}/{} MiB",
model.stats.vram.used.unwrap_or(0) / 1024 / 1024,
model.stats.vram.total.unwrap_or(0) / 1024 / 1024,
),
}
},
},
let PowerStats {
average: power_average,
current: power_current,
cap_current: power_cap_current,
..
} = stats.power;
append = &gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
set_spacing: 10,
set_homogeneous: true,
let power_current = power_current
.filter(|value| *value != 0.0)
.or(power_average);
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_hexpand: true,
set_spacing: 5,
self.set_power_usage(format!(
"<b>{:.1}/{} W</b>",
power_current.unwrap_or(0.0),
power_cap_current.unwrap_or(0.0)
));
InfoRow {
set_name: "GPU Core Clock (Average):",
#[watch]
set_value: format_clockspeed(model.stats.clockspeed.gpu_clockspeed, 1.0),
},
match &stats.throttle_info {
Some(throttle_info) => {
if throttle_info.is_empty() {
self.set_throttling("No")
} else {
let type_text: Vec<String> = throttle_info
.iter()
.map(|(throttle_type, details)| {
let mut out = throttle_type.to_string();
if !details.is_empty() {
let _ = write!(out, "({})", details.join(", "));
InfoRow {
set_name: "GPU Core Clock (Target):",
#[watch]
set_value: format_current_gfxclk(model.stats.clockspeed.current_gfxclk),
},
InfoRow {
set_name: "GPU Voltage:",
#[watch]
set_value: format!("{:.3} V", model.stats.voltage.gpu.unwrap_or(0) as f64 / 1000f64),
},
InfoRow {
set_name: "GPU Temperature (hotspot):",
#[watch]
set_value: {
let temperature = if model.stats.temps.len() == 1 {
model.stats.temps.values().next().unwrap().current
} else {
model.stats
.temps
.get("junction")
.or_else(|| model.stats.temps.get("edge"))
.and_then(|temp| temp.current)
}
out
})
.collect();
let text = type_text.join(", ");
self.set_throttling(text);
.unwrap_or(0.0);
format!("{temperature}°C")
},
},
},
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_hexpand: true,
set_spacing: 5,
InfoRow {
set_name: "GPU Memory Clock:",
#[watch]
set_value: format_clockspeed(
model.stats.clockspeed.vram_clockspeed,
model.vram_clock_ratio,
),
},
InfoRow {
set_name: "GPU Usage:",
#[watch]
set_value: format!("{}%", model.stats.busy_percent.unwrap_or(0)),
},
InfoRow {
set_name: "Power Usage:",
#[watch]
set_value: {
let PowerStats {
average: power_average,
current: power_current,
cap_current: power_cap_current,
..
} = model.stats.power;
let power_current = power_current
.filter(|value| *value != 0.0)
.or(power_average);
format!(
"<b>{:.1}/{} W</b>",
power_current.unwrap_or(0.0),
power_cap_current.unwrap_or(0.0)
)
}
},
InfoRow {
set_name: "Throttling:",
#[watch]
set_value: {
match &model.stats.throttle_info {
Some(throttle_info) => {
if throttle_info.is_empty() {
"No".to_owned()
} else {
let type_text: Vec<String> = throttle_info
.iter()
.map(|(throttle_type, details)| {
let mut out = throttle_type.to_string();
if !details.is_empty() {
let _ = write!(out, "({})", details.join(", "));
}
out
})
.collect();
type_text.join(", ")
}
}
None => "Unknown".to_owned(),
}
}
}
}
},
append = &gtk::Button {
set_label: "Show historical charts",
set_action_name: Some("app.show-graphs-window"),
},
}
}
fn init(
_init: Self::Init,
root: Self::Root,
_sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let model = Self {
stats: Arc::new(DeviceStats::default()),
vram_clock_ratio: 1.0,
};
let widgets = view_output!();
ComponentParts { widgets, model }
}
fn update(&mut self, msg: Self::Input, _sender: relm4::ComponentSender<Self>) {
match msg {
PageUpdate::Info(info) => {
self.vram_clock_ratio = info.vram_clock_ratio();
}
PageUpdate::Stats(stats) => {
self.stats = stats;
}
None => self.set_throttling("Unknown"),
}
}
}
impl Default for GpuStatsSection {
fn default() -> Self {
Self::new()
}
}
mod imp {
use crate::app::{info_row::InfoRow, page_section::PageSection};
use gtk::{
glib::{self, subclass::InitializingObject, types::StaticTypeExt, Properties},
prelude::ObjectExt,
subclass::{
prelude::*,
widget::{CompositeTemplateClass, WidgetImpl},
},
CompositeTemplate,
};
use std::cell::{Cell, RefCell};
#[derive(CompositeTemplate, Default, Properties)]
#[properties(wrapper_type = super::GpuStatsSection)]
#[template(file = "ui/oc_page/gpu_stats_section.blp")]
pub struct GpuStatsSection {
#[property(get, set)]
core_clock: RefCell<String>,
#[property(get, set)]
current_core_clock: RefCell<String>,
#[property(get, set)]
vram_clock: RefCell<String>,
#[property(get, set)]
voltage: RefCell<String>,
#[property(get, set)]
temperature: RefCell<String>,
#[property(get, set)]
gpu_usage: RefCell<String>,
#[property(get, set)]
power_usage: RefCell<String>,
#[property(get, set)]
vram_usage: RefCell<f64>,
#[property(get, set)]
vram_usage_text: RefCell<String>,
#[property(get, set)]
throttling: RefCell<String>,
#[property(get, set)]
vram_clock_ratio: Cell<f64>,
}
#[glib::object_subclass]
impl ObjectSubclass for GpuStatsSection {
const NAME: &'static str = "GpuStatsSection";
type Type = super::GpuStatsSection;
type ParentType = PageSection;
fn class_init(class: &mut Self::Class) {
InfoRow::ensure_type();
class.bind_template();
}
fn instance_init(obj: &InitializingObject<Self>) {
obj.init_template();
}
}
#[glib::derived_properties]
impl ObjectImpl for GpuStatsSection {}
impl WidgetImpl for GpuStatsSection {}
impl BoxImpl for GpuStatsSection {}
}
fn format_clockspeed(value: Option<u64>, ratio: f64) -> String {
format!("{:.3} GHz", value.unwrap_or(0) as f64 / 1000.0 * ratio)
}

View File

@@ -1,92 +0,0 @@
using Gtk 4.0;
template $GpuStatsSection: $PageSection {
name: "Statistics";
spacing: 10;
Box {
orientation: horizontal;
spacing: 5;
Label {
label: "VRAM Usage:";
}
Overlay {
LevelBar vram_usage_bar {
hexpand: true;
value: bind template.vram-usage;
orientation: horizontal;
}
[overlay]
Label vram_usage_label {
label: bind template.vram-usage-text;
}
}
}
Box {
orientation: horizontal;
spacing: 10;
homogeneous: true;
Box {
orientation: vertical;
hexpand: true;
spacing: 5;
$InfoRow {
name: "GPU Core Clock (Average):";
value: bind template.core-clock;
}
$InfoRow {
name: "GPU Core Clock (Target):";
value: bind template.current-core-clock;
}
$InfoRow {
name: "GPU Voltage:";
value: bind template.voltage;
}
$InfoRow {
name: "GPU Temperature (hotspot):";
value: bind template.temperature;
}
}
Box {
orientation: vertical;
hexpand: true;
spacing: 5;
$InfoRow {
name: "GPU Memory Clock:";
value: bind template.vram-clock;
}
$InfoRow {
name: "GPU Usage:";
value: bind template.gpu-usage;
}
$InfoRow {
name: "Power Usage:";
value: bind template.power-usage;
}
$InfoRow {
name: "Throttling:";
value: bind template.throttling;
}
}
}
Button {
label: "Show historical charts";
action-name: "app.show-graphs-window";
}
}