feat: support multi-column layout for adjustment-rows lists (#892)

* move box styles

* split into 2 lists

* wrap into flow-box

* clean up

clean up

clean up

clean up

replace get_factory with less verbose set_clock method

* fmt

* split Adjustment rows into multiple groups

* try columns

* wip

* finish markup

* move nvidia OC info popover

* fmt

* remove comments from schema

* fmt

* refactor

* add adjustment groups

* change warning text

* upd spacing

* move AdjustmentGroups

* refactor: make AdjustmentGroup a factory component, create them dynamically

* fix layout

* chore: drop unused param

* remove misleading comment

---------

Co-authored-by: Ilya Zlobintsev <ilya.zl@protonmail.com>
This commit is contained in:
Roman Makarov
2026-02-21 22:36:29 +02:00
committed by GitHub
co-authored by Ilya Zlobintsev
parent 02037ddea6
commit d39b963ca3
8 changed files with 414 additions and 189 deletions
+1 -1
View File
@@ -154,7 +154,7 @@ nvidia-oc-description =
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.
oc-warning = Warning: changing these values may lead to system instability and can potentially damage your hardware!
oc-warning = Changing these values may lead to system instability and can potentially damage your hardware!
show-all-pstates = Show all P-States
enable-gpu-locked-clocks = Enable GPU Locked Clocks
enable-vram-locked-clocks = Enable VRAM Locked Clocks
+5 -1
View File
@@ -1,3 +1,7 @@
.page-section-content {
border-radius: 5px;
}
}
.page-section-children-box {
margin: 10px;
}
+1 -1
View File
@@ -102,8 +102,8 @@ mod imp {
#[local_ref]
append = children_box {
add_css_class: "page-section-children-box",
set_orientation: gtk::Orientation::Vertical,
set_margin_all: 10,
set_spacing: 10,
}
}
@@ -0,0 +1,17 @@
.clocks-frame > .page-section-content {
border-color: transparent;
}
.clocks-frame .page-section-children-box {
margin: 0;
}
.clocks-frame-group {
padding: 0;
}
.clocks-frame-group .frame {
border-radius: 5px;
padding: 10px;
}
+179 -133
View File
@@ -1,10 +1,15 @@
mod adjustment_group;
mod adjustment_row;
use crate::{
APP_BROKER, I18N,
app::{msg::AppMsg, page_section::PageSection},
app::{
msg::AppMsg, page_section::PageSection,
pages::oc_page::clocks_frame::adjustment_group::AdjustmentGroup,
},
};
use adjustment_row::{ClockAdjustmentRow, ClockAdjustmentRowMsg, ClocksData};
use adjustment_group::ClockCategory;
use adjustment_row::ClocksData;
use amdgpu_sysfs::gpu_handle::overdrive::ClocksTableGen as AmdClocksTable;
use gtk::{
glib::object::ObjectExt,
@@ -21,11 +26,11 @@ use relm4::{
factory::FactoryHashMap,
};
// This should not end up being used in practice
const DEFAULT_VOLTAGE_OFFSET_RANGE: i32 = 250;
pub struct ClocksFrame {
clocks: FactoryHashMap<ClockspeedType, ClockAdjustmentRow>,
core_groups: FactoryHashMap<ClockCategory, AdjustmentGroup>,
vram_groups: FactoryHashMap<ClockCategory, AdjustmentGroup>,
vram_clock_ratio: f64,
show_nvidia_options: bool,
show_all_pstates: BoolBinding,
@@ -49,43 +54,17 @@ impl relm4::Component for ClocksFrame {
view! {
PageSection::new(&fl!(I18N, "overclock-section")) {
append_header = &gtk::Button {
set_label: &fl!(I18N, "reset-button"),
set_tooltip_text: Some(&fl!(I18N, "reset-oc-tooltip")),
add_css_class: "clocks-frame",
set_halign: gtk::Align::End,
append_header = &gtk::Box {
set_spacing: 10,
set_hexpand: true,
add_css_class: css::DESTRUCTIVE_ACTION,
#[watch]
set_visible: !model.clocks.is_empty(),
connect_clicked => move |_| {
APP_BROKER.send(AppMsg::ResetClocks);
}
},
append_child = &gtk::Label {
set_label: &fl!(I18N, "oc-warning"),
set_wrap_mode: pango::WrapMode::Word,
set_halign: gtk::Align::Start,
set_margin_horizontal: 5,
add_css_class: css::WARNING,
},
append_child = &gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
set_halign: gtk::Align::Start,
set_spacing: 5,
#[watch]
set_visible: model.show_nvidia_options,
append = &gtk::Label {
set_label: &fl!(I18N, "nvidia-oc-info"),
add_css_class: css::HEADING,
},
set_halign: gtk::Align::End,
append = &gtk::MenuButton {
set_icon_name: "dialog-information-symbolic",
#[watch]
set_visible: model.show_nvidia_options,
set_label: &fl!(I18N, "nvidia-oc-info"),
#[wrap(Some)]
set_popover = &gtk::Popover {
@@ -97,6 +76,28 @@ impl relm4::Component for ClocksFrame {
}
}
},
append = &gtk::Button {
set_label: &fl!(I18N, "reset-button"),
set_tooltip_text: Some(&fl!(I18N, "reset-oc-tooltip")),
add_css_class: css::DESTRUCTIVE_ACTION,
#[watch]
set_visible: model.has_any_clocks(),
connect_clicked => move |_| {
APP_BROKER.send(AppMsg::ResetClocks);
}
},
},
append_child = &gtk::Label {
set_label: &fl!(I18N, "oc-warning"),
set_wrap_mode: pango::WrapMode::Word,
set_halign: gtk::Align::Start,
add_css_class: css::WARNING,
add_css_class: css::DIM_LABEL,
},
append_child = &gtk::Box {
@@ -110,7 +111,8 @@ impl relm4::Component for ClocksFrame {
append = &gtk::CheckButton {
#[watch]
set_visible: model.clocks.values().any(|row| row.is_secondary),
set_visible: model.any_is_secondary(),
set_label: Some(&fl!(I18N, "show-all-pstates")),
add_binding["active"]: &model.show_all_pstates,
},
@@ -139,7 +141,6 @@ impl relm4::Component for ClocksFrame {
append = &gtk::Label {
#[watch]
set_visible: model.show_all_pstates.value() && model.show_nvidia_options,
// add_binding["visible"]: &model.show_all_pstates,
set_margin_horizontal: 5,
set_markup: &fl!(I18N, "pstate-list-description"),
@@ -148,10 +149,46 @@ impl relm4::Component for ClocksFrame {
},
},
append_child = model.clocks.widget() {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 5,
set_margin_horizontal: 5,
append_child = &gtk::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 = &gtk::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 = &gtk::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 = &gtk::Label {
@@ -159,7 +196,7 @@ impl relm4::Component for ClocksFrame {
set_margin_horizontal: 10,
set_halign: gtk::Align::Start,
#[watch]
set_visible: model.clocks.is_empty(),
set_visible: !model.has_any_clocks(),
},
}
}
@@ -169,10 +206,9 @@ impl relm4::Component for ClocksFrame {
root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let clocks = FactoryHashMap::builder().launch_default().detach();
let model = Self {
clocks,
core_groups: FactoryHashMap::builder().launch_default().detach(),
vram_groups: FactoryHashMap::builder().launch_default().detach(),
vram_clock_ratio: 1.0,
show_nvidia_options: false,
show_all_pstates: BoolBinding::new(false),
@@ -191,6 +227,9 @@ impl relm4::Component for ClocksFrame {
});
}
let core_groups_widget = model.core_groups.widget();
let vram_groups_widget = model.vram_groups.widget();
let widgets = view_output!();
ComponentParts { model, widgets }
@@ -212,7 +251,9 @@ impl relm4::Component for ClocksFrame {
.vram_locked_clocks_togglebutton
.block_signal(&widgets.vram_locked_clock_signal);
self.clocks.clear();
self.core_groups.clear();
self.vram_groups.clear();
self.enable_gpu_locked_clocks.set_value(false);
self.enable_vram_locked_clocks.set_value(false);
self.show_nvidia_options = false;
@@ -225,18 +266,11 @@ impl relm4::Component for ClocksFrame {
}
}
// Make sure the width of all the labels is the same
let label_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal);
let input_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal);
for clockspeed_type in self.clocks.keys() {
self.clocks.send(
clockspeed_type,
ClockAdjustmentRowMsg::AddSizeGroup {
label_group: label_size_group.clone(),
input_group: input_size_group.clone(),
},
);
for group in self.all_groups() {
group.add_size_group(label_size_group.clone(), input_size_group.clone());
}
widgets
@@ -252,25 +286,13 @@ impl relm4::Component for ClocksFrame {
self.vram_clock_ratio = vram_ratio;
}
ClocksFrameMsg::TogglePStatesVisibility => {
let show_secondary = self.show_all_pstates.value();
for (key, row) in self.clocks.iter() {
// Only show min/max core/vram clock when nvidia locked clocks are enabeld
let show_current = match key {
ClockspeedType::MaxCoreClock | ClockspeedType::MinCoreClock
if self.show_nvidia_options =>
{
self.enable_gpu_locked_clocks.value()
}
ClockspeedType::MaxMemoryClock | ClockspeedType::MinMemoryClock
if self.show_nvidia_options =>
{
self.enable_vram_locked_clocks.value()
}
_ => !row.is_secondary || show_secondary,
};
self.clocks
.send(key, ClockAdjustmentRowMsg::SetVisible(show_current));
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(),
);
}
}
}
@@ -281,17 +303,50 @@ impl relm4::Component for ClocksFrame {
}
impl ClocksFrame {
fn set_clock(&mut self, clock_type: ClockspeedType, data: ClocksData) {
let category = ClockCategory::from_type(clock_type);
let groups = if category.is_core() {
&mut self.core_groups
} else if category.is_vram() {
&mut self.vram_groups
} 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<Item = &AdjustmentGroup> {
self.core_groups.values().chain(self.vram_groups.values())
}
fn has_any_clocks(&self) -> bool {
self.core_groups.values().any(|group| !group.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 update_vram_clock_ratio(&self) {
for clock_type in [
ClockspeedType::MaxMemoryClock,
ClockspeedType::MinMemoryClock,
] {
if self.clocks.get(&clock_type).is_some() {
self.clocks.send(
&clock_type,
ClockAdjustmentRowMsg::ValueRatio(self.vram_clock_ratio),
);
}
if let Some(vram_group) = self.vram_groups.get(&ClockCategory::VramClock) {
vram_group.set_value_ratio(self.vram_clock_ratio);
}
}
@@ -306,7 +361,6 @@ impl ClocksFrame {
ClockspeedType::GpuVfCurveClock,
min_sclk,
max_sclk,
true,
);
}
@@ -318,7 +372,6 @@ impl ClocksFrame {
ClockspeedType::MemVfCurveClock,
min_mclk,
max_mclk,
false,
);
}
@@ -328,7 +381,6 @@ impl ClocksFrame {
ClockspeedType::GpuVfCurveVoltage,
min_vddc,
max_vddc,
false,
);
self.add_amd_list(
@@ -336,7 +388,6 @@ impl ClocksFrame {
ClockspeedType::MemVfCurveVoltage,
min_vddc,
max_vddc,
false,
);
}
}
@@ -348,7 +399,7 @@ impl ClocksFrame {
.and_then(|range| range.into_full())
&& let Some(sclk_offset) = table.sclk_offset
{
self.clocks.insert(
self.set_clock(
ClockspeedType::GpuClockOffset(0),
ClocksData {
current: sclk_offset,
@@ -356,7 +407,6 @@ impl ClocksFrame {
max: sclk_offset_max,
custom_title: Some(fl!(I18N, "gpu-clock-offset")),
is_secondary: false,
show_separator: false,
},
);
}
@@ -370,13 +420,11 @@ impl ClocksFrame {
ClockspeedType::MaxCoreClock,
table.current_sclk_range.max,
table.od_range.sclk,
false,
),
(
ClockspeedType::MinCoreClock,
table.current_sclk_range.min,
table.od_range.sclk,
false,
),
]);
} else {
@@ -389,7 +437,7 @@ impl ClocksFrame {
.or(table.od_range.sclk.as_ref())
.and_then(|range| range.into_full())
{
self.clocks.insert(
self.set_clock(
ClockspeedType::GpuVfCurveClock(i as u8),
ClocksData {
current: level.clockspeed,
@@ -397,7 +445,6 @@ impl ClocksFrame {
max: max_sclk,
is_secondary: false,
custom_title: None,
show_separator: false,
},
);
}
@@ -410,7 +457,7 @@ impl ClocksFrame {
.get(i)
.and_then(|range| range.into_full())
{
self.clocks.insert(
self.set_clock(
ClockspeedType::GpuVfCurveVoltage(i as u8),
ClocksData {
current: level.voltage,
@@ -418,7 +465,6 @@ impl ClocksFrame {
max: max_vddc,
is_secondary: false,
custom_title: None,
show_separator: i == table.vddc_curve.len() - 1, // Show on first row (reversed count)
},
);
}
@@ -430,26 +476,19 @@ impl ClocksFrame {
ClockspeedType::MaxMemoryClock,
table.current_mclk_range.max,
table.od_range.mclk,
true,
),
(
ClockspeedType::MinMemoryClock,
table.current_mclk_range.min,
table.od_range.mclk,
false,
),
]);
for (clockspeed_type, current_value, range, show_separator) in clocks_types {
for (clockspeed_type, current_value, range) in clocks_types {
if let Some(current) = current_value
&& let Some((min, max)) = range.and_then(|range| range.into_full())
{
let mut data = ClocksData::new(current, min, max);
if show_separator && !self.clocks.is_empty() {
data.show_separator = true;
}
self.clocks.insert(clockspeed_type, data);
self.set_clock(clockspeed_type, ClocksData::new(current, min, max));
}
}
@@ -460,9 +499,10 @@ impl ClocksFrame {
.and_then(|range| range.into_full())
.unwrap_or((-DEFAULT_VOLTAGE_OFFSET_RANGE, DEFAULT_VOLTAGE_OFFSET_RANGE));
let mut data = ClocksData::new(current, min, max);
data.show_separator = true;
self.clocks.insert(ClockspeedType::VoltageOffset, data);
self.set_clock(
ClockspeedType::VoltageOffset,
ClocksData::new(current, min, max),
);
}
}
}
@@ -474,13 +514,12 @@ impl ClocksFrame {
clock_type: fn(u8) -> ClockspeedType,
min: i32,
max: i32,
disable_separator: bool,
) {
let values_len = values.len();
for (i, value) in values.enumerate().rev() {
let is_secondary = i > 0 && i < values_len - 1;
self.clocks.insert(
self.set_clock(
clock_type(i as u8),
ClocksData {
current: value,
@@ -488,7 +527,6 @@ impl ClocksFrame {
max,
is_secondary,
custom_title: None,
show_separator: !disable_separator && i == values_len - 1, // Show on first row (reversed count)
},
);
}
@@ -503,14 +541,14 @@ impl ClocksFrame {
table.gpu_locked_clocks,
ClockspeedType::MinCoreClock,
ClockspeedType::MaxCoreClock,
&self.enable_gpu_locked_clocks,
self.enable_gpu_locked_clocks.clone(),
),
(
table.vram_clock_range,
table.vram_locked_clocks,
ClockspeedType::MinMemoryClock,
ClockspeedType::MaxMemoryClock,
&self.enable_vram_locked_clocks,
self.enable_vram_locked_clocks.clone(),
),
];
@@ -524,11 +562,11 @@ impl ClocksFrame {
None => (gpu_min, gpu_max),
};
self.clocks.insert(
self.set_clock(
min_type,
ClocksData::new(current_min as i32, gpu_min as i32, gpu_max as i32),
);
self.clocks.insert(
self.set_clock(
max_type,
ClocksData::new(current_max as i32, gpu_min as i32, gpu_max as i32),
);
@@ -536,13 +574,13 @@ impl ClocksFrame {
}
for (pstate, offset) in table.gpu_offsets {
self.clocks.insert(
self.set_clock(
ClockspeedType::GpuClockOffset(pstate),
nvidia_clock_offset_to_data(&offset, pstate > 0),
);
}
for (pstate, offset) in table.mem_offsets {
self.clocks.insert(
self.set_clock(
ClockspeedType::MemClockOffset(pstate),
nvidia_clock_offset_to_data(&offset, pstate > 0),
);
@@ -555,11 +593,11 @@ impl ClocksFrame {
if let Some((current_gt_min, current_gt_max)) = table.gt_freq
&& let (Some(min_clock), Some(max_clock)) = (table.rpn_freq, table.rp0_freq)
{
self.clocks.insert(
self.set_clock(
ClockspeedType::MaxCoreClock,
ClocksData::new(current_gt_max as i32, min_clock as i32, max_clock as i32),
);
self.clocks.insert(
self.set_clock(
ClockspeedType::MinCoreClock,
ClocksData::new(current_gt_min as i32, min_clock as i32, max_clock as i32),
);
@@ -567,30 +605,39 @@ impl ClocksFrame {
}
pub fn get_commands(&self) -> Vec<SetClocksCommand> {
self.clocks
.iter()
.filter_map(|(clock_type, row)| {
// If nvidia options are enabled, we always set locked clocks to None or Some
self.all_groups()
.flat_map(|group| group.get_commands())
.map(|(clock_type, configured_value)| {
let value = if self.show_nvidia_options {
match clock_type {
ClockspeedType::MinCoreClock | ClockspeedType::MaxCoreClock => self
.enable_gpu_locked_clocks
.value()
.then(|| row.get_raw_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(|| row.get_raw_value()),
_ => Some(row.get_configured_value()?),
.then(|| {
self.vram_groups
.get(&ClockCategory::from_type(clock_type))
.map(|group| group.get_raw_value(clock_type))
})
.flatten(),
_ => configured_value,
}
} else {
Some(row.get_configured_value()?)
configured_value
};
Some(SetClocksCommand {
r#type: *clock_type,
SetClocksCommand {
r#type: clock_type,
value,
})
}
})
.collect()
}
@@ -603,6 +650,5 @@ fn nvidia_clock_offset_to_data(clock_info: &NvidiaClockOffset, is_secondary: boo
max: clock_info.max,
custom_title: None,
is_secondary,
show_separator: false,
}
}
@@ -0,0 +1,160 @@
use super::adjustment_row::{ClockAdjustmentRow, ClockAdjustmentRowMsg, ClocksData};
use gtk::prelude::{BoxExt, OrientableExt, WidgetExt};
use lact_schema::request::ClockspeedType;
use relm4::{css, factory::FactoryHashMap, prelude::FactoryComponent};
#[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 => 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<ClockspeedType, ClockAdjustmentRow>,
}
impl AdjustmentGroup {
pub fn is_empty(&self) -> bool {
self.adjustments.is_empty()
}
pub fn has_secondary(&self) -> bool {
self.adjustments.values().any(|row| row.is_secondary)
}
pub fn set_clock(&mut self, clock_type: ClockspeedType, data: ClocksData) {
self.adjustments.insert(clock_type, data);
}
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,
ClockAdjustmentRowMsg::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, ClockAdjustmentRowMsg::ValueRatio(ratio));
}
}
pub fn toggle_secondary_visibility(
&self,
show_secondary: bool,
show_nvidia_options: bool,
enable_gpu_locked: bool,
enable_vram_locked: bool,
) {
for (key, row) in self.adjustments.iter() {
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
}
_ => !row.is_secondary || show_secondary,
};
self.adjustments
.send(key, ClockAdjustmentRowMsg::SetVisible(show_current));
}
}
pub fn get_commands(&self) -> Vec<(ClockspeedType, Option<i32>)> {
self.adjustments
.iter()
.map(|(clock_type, row)| (*clock_type, row.get_configured_value()))
.collect()
}
pub fn get_raw_value(&self, clock_type: ClockspeedType) -> i32 {
self.adjustments
.get(&clock_type)
.map(|row| row.get_raw_value())
.unwrap_or(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::FRAME,
}
}
fn init_model(_: Self::Init, _: &Self::Index, _: relm4::FactorySender<Self>) -> Self {
Self {
adjustments: FactoryHashMap::builder().launch_default().detach(),
}
}
}
@@ -2,6 +2,7 @@ use crate::{
APP_BROKER, I18N,
app::{msg::AppMsg, pages::oc_adjustment::OcAdjustment},
};
use gtk::prelude::BoxExt;
use gtk::{
glib::{
SignalHandlerId,
@@ -12,7 +13,7 @@ use gtk::{
};
use i18n_embed_fl::fl;
use lact_schema::request::ClockspeedType;
use relm4::{RelmWidgetExt, prelude::FactoryComponent};
use relm4::prelude::FactoryComponent;
pub struct ClockAdjustmentRow {
clock_type: ClockspeedType,
@@ -20,7 +21,6 @@ pub struct ClockAdjustmentRow {
value_ratio: f64,
change_signal: SignalHandlerId,
adjustment: OcAdjustment,
show_separator: bool,
pub(super) is_secondary: bool,
}
@@ -30,7 +30,6 @@ pub struct ClocksData {
pub max: i32,
pub custom_title: Option<String>,
pub is_secondary: bool,
pub show_separator: bool,
}
impl ClocksData {
@@ -41,46 +40,10 @@ impl ClocksData {
max,
is_secondary: false,
custom_title: None,
show_separator: false,
}
}
}
fn make_event_controller_no_scroll() -> gtk::EventControllerScroll {
let controller = gtk::EventControllerScroll::new(
gtk::EventControllerScrollFlags::VERTICAL | gtk::EventControllerScrollFlags::HORIZONTAL,
);
controller.connect_scroll(|controller, dx, dy| {
if let Some(parent) = controller
.widget()
.and_then(|widget| widget.ancestor(gtk::ScrolledWindow::static_type()))
{
let scrolled_window = parent.downcast::<gtk::ScrolledWindow>().unwrap();
if dy != 0.0 {
let current = scrolled_window.vadjustment().value();
let step = scrolled_window.vadjustment().step_increment();
// This is a bit of a hack, fractional values are generally touchpad inputs (in pixels),
// while whole values are scroll wheel events (which should use the `step` value)
// With newer GTK this should be changed to getting `unit()` from the scroll controller
let delta = if dy.fract() == 0.0 { dy * step } else { dy };
scrolled_window.vadjustment().set_value(current + delta);
}
if dx != 0.0 {
let current = scrolled_window.hadjustment().value();
let step = scrolled_window.hadjustment().step_increment();
let delta = if dx.fract() == 0.0 { dy * step } else { dy };
scrolled_window.hadjustment().set_value(current + delta);
}
}
gtk::glib::Propagation::Stop
});
controller
}
#[derive(Debug)]
pub enum ClockAdjustmentRowMsg {
ValueRatio(f64),
@@ -105,14 +68,10 @@ impl FactoryComponent for ClockAdjustmentRow {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
gtk::Separator {
set_visible: self.show_separator,
set_margin_top: 5,
set_margin_bottom: 10,
},
gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
set_spacing: 5,
#[name = "title_label"]
gtk::Label {
set_xalign: 0.0,
@@ -147,7 +106,7 @@ impl FactoryComponent for ClockAdjustmentRow {
set_digits: 0,
set_round_digits: 0,
set_value_pos: gtk::PositionType::Right,
set_margin_horizontal: 5,
set_width_request: 150,
add_controller = make_event_controller_no_scroll(),
},
@@ -184,7 +143,6 @@ impl FactoryComponent for ClockAdjustmentRow {
change_signal,
value_ratio: 1.0,
is_secondary: data.is_secondary,
show_separator: data.show_separator,
}
}
@@ -237,3 +195,38 @@ impl ClockAdjustmentRow {
(self.adjustment.value() / self.value_ratio) as i32
}
}
fn make_event_controller_no_scroll() -> gtk::EventControllerScroll {
let controller = gtk::EventControllerScroll::new(
gtk::EventControllerScrollFlags::VERTICAL | gtk::EventControllerScrollFlags::HORIZONTAL,
);
controller.connect_scroll(|controller, dx, dy| {
if let Some(parent) = controller
.widget()
.and_then(|widget| widget.ancestor(gtk::ScrolledWindow::static_type()))
{
let scrolled_window = parent.downcast::<gtk::ScrolledWindow>().unwrap();
if dy != 0.0 {
let current = scrolled_window.vadjustment().value();
let step = scrolled_window.vadjustment().step_increment();
// This is a bit of a hack, fractional values are generally touchpad inputs (in pixels),
// while whole values are scroll wheel events (which should use the `step` value)
// With newer GTK this should be changed to getting `unit()` from the scroll controller
let delta = if dy.fract() == 0.0 { dy * step } else { dy };
scrolled_window.vadjustment().set_value(current + delta);
}
if dx != 0.0 {
let current = scrolled_window.hadjustment().value();
let step = scrolled_window.hadjustment().step_increment();
let delta = if dx.fract() == 0.0 { dy * step } else { dy };
scrolled_window.hadjustment().set_value(current + delta);
}
}
gtk::glib::Propagation::Stop
});
controller
}
+11 -6
View File
@@ -137,18 +137,23 @@ impl SetClocksCommand {
#[serde(rename_all = "snake_case")]
pub enum ClockspeedType {
MaxCoreClock,
MaxMemoryClock,
MaxVoltage,
MinCoreClock,
MinMemoryClock,
MinVoltage,
VoltageOffset,
GpuClockOffset(u32),
MinVoltage,
MaxVoltage,
VoltageOffset,
MaxMemoryClock,
MinMemoryClock,
MemClockOffset(u32),
GpuVfCurveClock(u8),
MemVfCurveClock(u8),
GpuVfCurveVoltage(u8),
MemVfCurveClock(u8),
MemVfCurveVoltage(u8),
Reset,
}