use super::{ConfigLoadError, ConfigPersistence, MenuConfig, PerformanceLevel};
use crate::core::{Color, HorizontalAlignment, Point, Rect};
use crate::event::{Event, EventHandler};
use crate::impl_draw_bridge;
use crate::render::RenderContext;
use crate::signal::GenericSignal;
use crate::widget::metrics::{dimensions, effective_font};
use crate::widget::{BaseWidget, Draw, Widget, WidgetKind};
use std::io;
pub struct MenuConfigDialog {
config: MenuConfig,
persistence: ConfigPersistence,
}
impl MenuConfigDialog {
pub fn new(config: MenuConfig) -> Self {
Self { config, persistence: ConfigPersistence::new() }
}
pub fn with_persistence(config: MenuConfig, persistence: ConfigPersistence) -> Self {
Self { config, persistence }
}
pub fn config(&self) -> &MenuConfig {
&self.config
}
pub fn config_mut(&mut self) -> &mut MenuConfig {
&mut self.config
}
pub fn is_enabled(&self) -> bool {
self.config.is_enabled()
}
pub fn toggle_enabled(&mut self) {
let new_value = !self.config.is_enabled();
self.config.set_enabled(new_value);
}
pub fn toggle_animations(&mut self) {
let new_value = !self.config.animations_enabled();
self.config.set_animations_enabled(new_value);
}
pub fn toggle_transparency(&mut self) {
let new_value = !self.config.transparency_enabled();
self.config.set_transparency_enabled(new_value);
}
pub fn toggle_shadows(&mut self) {
let new_value = !self.config.shadows_enabled();
self.config.set_shadows_enabled(new_value);
}
pub fn toggle_blur(&mut self) {
let new_value = !self.config.blur_enabled();
self.config.set_blur_enabled(new_value);
}
pub fn increase_animation_speed(&mut self) {
let current = self.config.animation_speed();
let new_speed = (current + 0.1).min(3.0);
self.config.set_animation_speed(new_speed);
}
pub fn decrease_animation_speed(&mut self) {
let current = self.config.animation_speed();
let new_speed = (current - 0.1).max(0.1);
self.config.set_animation_speed(new_speed);
}
pub fn increase_max_items(&mut self) {
let current = self.config.max_visible_items();
self.config.set_max_visible_items(current.saturating_add(5));
}
pub fn decrease_max_items(&mut self) {
let current = self.config.max_visible_items();
if current > 5 {
self.config.set_max_visible_items(current - 5);
}
}
pub fn reset_to_defaults(&mut self) {
self.config.reset_to_defaults();
}
pub fn save(&self) -> io::Result<()> {
self.persistence.save(&self.config)
}
pub fn load(&mut self) -> Result<(), ConfigLoadError> {
let overrides = self.persistence.load()?;
self.config.reload_overrides(overrides);
Ok(())
}
pub fn settings_summary(&self) -> String {
self.config.settings_summary()
}
pub fn has_overrides(&self) -> bool {
self.config.has_user_overrides()
}
pub fn hardware_performance_level(&self) -> PerformanceLevel {
self.config.hardware_caps().performance_level
}
pub fn gpu_description(&self) -> String {
let caps = self.config.hardware_caps();
match (caps.gpu_memory_mb, caps.gpu_memory_is_measured) {
(Some(mb), true) => format!("{} ({mb} MB)", caps.gpu_type.description()),
_ => format!("{} (memory not reportable)", caps.gpu_type.description()),
}
}
pub fn system_memory_description(&self) -> String {
match self.config.hardware_caps().system_ram_mb {
Some(mb) => format!("{mb} MB"),
None => format!(
"not reportable on this backend (scoring assumes {} MB)",
MenuConfig::assumed_system_memory()
),
}
}
}
struct ConfigRow {
label: &'static str,
read: fn(&MenuConfig) -> bool,
toggle: fn(&mut MenuConfig),
}
const CONFIG_ROWS: &[ConfigRow] = &[
ConfigRow {
label: "Adaptive configuration",
read: MenuConfig::is_enabled,
toggle: |config| config.set_enabled(!config.is_enabled()),
},
ConfigRow {
label: "Animations",
read: MenuConfig::animations_enabled,
toggle: |config| config.set_animations_enabled(!config.animations_enabled()),
},
ConfigRow {
label: "Transparency",
read: MenuConfig::transparency_enabled,
toggle: |config| {
let next = !config.transparency_enabled();
config.set_transparency_enabled(next);
},
},
ConfigRow {
label: "Shadows",
read: MenuConfig::shadows_enabled,
toggle: |config| {
let next = !config.shadows_enabled();
config.set_shadows_enabled(next);
},
},
ConfigRow {
label: "Blur",
read: MenuConfig::blur_enabled,
toggle: |config| {
let next = !config.blur_enabled();
config.set_blur_enabled(next);
},
},
ConfigRow {
label: "Hardware acceleration",
read: MenuConfig::hardware_acceleration,
toggle: |config| {
let next = !config.hardware_acceleration();
config.set_hardware_acceleration(next);
},
},
ConfigRow {
label: "Text measurement cache",
read: MenuConfig::caching_enabled,
toggle: |config| {
let next = !config.caching_enabled();
config.set_caching_enabled(next);
},
},
];
pub struct MenuConfigPanel {
base: BaseWidget,
config: MenuConfig,
hovered_row: Option<usize>,
pub changed: GenericSignal,
}
impl MenuConfigPanel {
pub fn new(config: MenuConfig, geometry: Rect) -> Self {
Self {
base: BaseWidget::new(WidgetKind::Menu, geometry, "MenuConfigPanel"),
config,
hovered_row: None,
changed: GenericSignal::new(),
}
}
pub fn config(&self) -> &MenuConfig {
&self.config
}
pub fn config_mut(&mut self) -> &mut MenuConfig {
&mut self.config
}
pub fn row_count(&self) -> usize {
CONFIG_ROWS.len()
}
pub fn row_label(&self, index: usize) -> Option<&'static str> {
CONFIG_ROWS.get(index).map(|row| row.label)
}
pub fn row_value(&self, index: usize) -> Option<bool> {
CONFIG_ROWS.get(index).map(|row| (row.read)(&self.config))
}
pub fn toggle_row(&mut self, index: usize) -> bool {
let Some(row) = CONFIG_ROWS.get(index) else {
return false;
};
(row.toggle)(&mut self.config);
self.base.request_redraw();
self.changed.emit();
true
}
fn row_height() -> u32 {
dimensions::MENU_ROW_PADDING_H.max(8) * 2 + 8
}
pub fn row_rect(&self, index: usize) -> Rect {
let geom = self.geometry();
let height = Self::row_height();
Rect::new(
geom.x,
geom.y + dimensions::MENU_POPUP_PADDING as i32 + index as i32 * height as i32,
geom.width,
height,
)
}
fn row_at(&self, pos: Point) -> Option<usize> {
(0..CONFIG_ROWS.len()).find(|index| self.row_rect(*index).contains_point(pos))
}
}
impl Widget for MenuConfigPanel {
fn base(&self) -> &BaseWidget {
&self.base
}
fn base_mut(&mut self) -> &mut BaseWidget {
&mut self.base
}
fn size_hint(&self) -> crate::core::Size {
let height =
dimensions::MENU_POPUP_PADDING * 2 + CONFIG_ROWS.len() as u32 * Self::row_height();
crate::core::Size::new(260, height)
}
impl_draw_bridge!();
}
impl EventHandler for MenuConfigPanel {
fn handle_event(&mut self, event: &Event) {
self.base.handle_event(event);
if !self.base.is_enabled() {
return;
}
match event {
Event::MouseMove { pos } => {
let next = self.row_at(*pos);
if next != self.hovered_row {
self.hovered_row = next;
self.base.request_redraw();
}
}
Event::MouseLeave { .. } => {
if self.hovered_row.take().is_some() {
self.base.request_redraw();
}
}
Event::MousePress { pos, button: 1, .. } => {
if let Some(index) = self.row_at(*pos) {
self.toggle_row(index);
}
}
_ => {}
}
}
}
impl Draw for MenuConfigPanel {
fn draw(&mut self, context: &mut RenderContext) {
let geom = self.geometry();
let style = self.base.style();
let surface = style.background_color.unwrap_or(Color::rgb(245, 245, 245));
let ink = style.text_color.unwrap_or_else(|| surface.contrast_color());
let accent = crate::style::theme_manager()
.current_theme()
.map(|theme| theme.colors.primary)
.unwrap_or(Color::rgb(0, 120, 215));
context.fill_rounded_rect(geom, 4, surface);
context.draw_rounded_rect_stroke(geom, 4, surface.blend(&ink, 0.3), 1);
let font = effective_font(style);
for (index, spec) in CONFIG_ROWS.iter().enumerate() {
let row = self.row_rect(index);
if self.hovered_row == Some(index) {
context.fill_rect(row, surface.blend(&accent, 0.14));
}
let line = context.text_line(row, font);
context.draw_text_fitted_cow(
Rect::new(row.x + 10, line.y, row.width.saturating_sub(60), line.height.max(1)),
spec.label,
font,
ink,
HorizontalAlignment::Left,
);
let on = (spec.read)(&self.config);
context.draw_text_fitted_cow(
Rect::new(row.x + row.width as i32 - 52, line.y, 42, line.height.max(1)),
if on { "On" } else { "Off" },
font,
if on { accent } else { ink.blend(&surface, 0.4) },
HorizontalAlignment::Right,
);
}
}
}