Click outside menu to close app

- Add toucharea outside menu
This commit is contained in:
Candifloss 2026-01-12 10:38:46 +05:30
parent 5f0ca85589
commit d0916291b4
3 changed files with 67 additions and 53 deletions

View File

@ -1,7 +1,7 @@
use serde::Deserialize;
use slint::{Color, SharedString};
use std::fs;
use std::path::PathBuf;
use slint::{Color, SharedString};
pub fn parse_hex_color(s: &str) -> Result<Color, String> {
let clean = s.trim().trim_start_matches('#');
@ -10,9 +10,7 @@ pub fn parse_hex_color(s: &str) -> Result<Color, String> {
return Err("color must be RRGGBBAA".into());
}
let raw =
u32::from_str_radix(clean, 16)
.map_err(|_| "invalid hex color")?;
let raw = u32::from_str_radix(clean, 16).map_err(|_| "invalid hex color")?;
Ok(Color::from_argb_encoded(raw))
}
@ -120,17 +118,12 @@ pub struct ButtonStyleResolved {
pub option_text_color: Color,
}
pub fn load_config(
screen_w: i32,
screen_h: i32,
) -> Result<ResolvedConfig, String> {
pub fn load_config(screen_w: i32, screen_h: i32) -> Result<ResolvedConfig, String> {
let path = config_path();
let raw = if path.exists() {
let text = fs::read_to_string(&path)
.map_err(|e| e.to_string())?;
toml::from_str::<Config>(&text)
.map_err(|e| e.to_string())?
let text = fs::read_to_string(&path).map_err(|e| e.to_string())?;
toml::from_str::<Config>(&text).map_err(|e| e.to_string())?
} else {
Config {
screen: None,
@ -145,9 +138,7 @@ pub fn load_config(
}
fn config_path() -> PathBuf {
let mut p =
dirs::config_dir()
.unwrap_or_else(|| PathBuf::from("."));
let mut p = dirs::config_dir().unwrap_or_else(|| PathBuf::from("."));
p.push("candywidgets");
p.push("power-menu");
@ -155,26 +146,19 @@ fn config_path() -> PathBuf {
p
}
fn resolve(
raw: Config,
fallback_w: i32,
fallback_h: i32,
) -> Result<ResolvedConfig, String> {
fn resolve(raw: Config, fallback_w: i32, fallback_h: i32) -> Result<ResolvedConfig, String> {
let screen = raw.screen.as_ref();
let menu = raw.menu_popup.as_ref();
let buttons = raw.buttons.as_ref();
let screen_width =
screen.and_then(|s| s.width).unwrap_or(fallback_w);
let screen_height =
screen.and_then(|s| s.height).unwrap_or(fallback_h);
let screen_width = screen.and_then(|s| s.width).unwrap_or(fallback_w);
let screen_height = screen.and_then(|s| s.height).unwrap_or(fallback_h);
let screen_bg =
screen
.and_then(|s| s.bg_color.as_deref())
.map(parse_hex_color)
.transpose()?
.unwrap_or(Color::from_argb_encoded(0x20A3A3A3));
let screen_bg = screen
.and_then(|s| s.bg_color.as_deref())
.map(parse_hex_color)
.transpose()?
.unwrap_or(Color::from_argb_encoded(0x20A3A3A3));
let btn_width = buttons.and_then(|b| b.width).unwrap_or(120);
let btn_height = buttons.and_then(|b| b.height).unwrap_or(120);
@ -188,13 +172,19 @@ fn resolve(
btn_border_radius: buttons.and_then(|b| b.border_radius).unwrap_or(20),
btn_bg_normal: parse_hex_color(
buttons.and_then(|b| b.bg_color_normal.as_deref()).unwrap_or("91919175")
buttons
.and_then(|b| b.bg_color_normal.as_deref())
.unwrap_or("91919175"),
)?,
btn_bg_hover: parse_hex_color(
buttons.and_then(|b| b.bg_color_hover.as_deref()).unwrap_or("92929284")
buttons
.and_then(|b| b.bg_color_hover.as_deref())
.unwrap_or("92929284"),
)?,
btn_bg_clicked: parse_hex_color(
buttons.and_then(|b| b.bg_color_clicked.as_deref()).unwrap_or("9B9B9BAB")
buttons
.and_then(|b| b.bg_color_clicked.as_deref())
.unwrap_or("9B9B9BAB"),
)?,
option_icon_height: icon_height,
@ -211,13 +201,13 @@ fn resolve(
let padding = menu.and_then(|m| m.padding_x).unwrap_or(20);
let spacing = menu.and_then(|m| m.button_spacing).unwrap_or(padding);
let menu_width =
menu.and_then(|m| m.width)
.unwrap_or(btn_width * 4 + spacing * 3 + padding * 2);
let menu_width = menu
.and_then(|m| m.width)
.unwrap_or(btn_width * 4 + spacing * 3 + padding * 2);
let menu_height =
menu.and_then(|m| m.height)
.unwrap_or(btn_height + padding * 2);
let menu_height = menu
.and_then(|m| m.height)
.unwrap_or(btn_height + padding * 2);
Ok(ResolvedConfig {
screen_width,
@ -233,8 +223,12 @@ fn resolve(
menu_width,
menu_height,
menu_pos_x: menu.and_then(|m| m.pos_x).unwrap_or((screen_width - menu_width) / 2),
menu_pos_y: menu.and_then(|m| m.pos_y).unwrap_or((screen_height - menu_height) / 2),
menu_pos_x: menu
.and_then(|m| m.pos_x)
.unwrap_or((screen_width - menu_width) / 2),
menu_pos_y: menu
.and_then(|m| m.pos_y)
.unwrap_or((screen_height - menu_height) / 2),
menu_bg: menu
.and_then(|m| m.bg_color.as_deref())
.map(parse_hex_color)

View File

@ -1,11 +1,9 @@
use slint::{Color, LogicalPosition, LogicalSize, SharedString};
use crate::config::ResolvedConfig;
use slint::{Color, LogicalPosition, LogicalSize, SharedString};
slint::include_modules!();
fn to_slint_button_style(
s: crate::config::ButtonStyleResolved,
) -> ButtonStyle {
fn to_slint_button_style(s: crate::config::ButtonStyleResolved) -> ButtonStyle {
ButtonStyle {
btn_width: s.btn_width,
btn_height: s.btn_height,
@ -27,9 +25,7 @@ fn to_slint_button_style(
}
}
pub fn run_power_menu(
cfg: ResolvedConfig
) -> Result<(), Box<dyn std::error::Error>> {
pub fn run_power_menu(cfg: ResolvedConfig) -> Result<(), Box<dyn std::error::Error>> {
let ui = PowerMenu::new()?;
let button_style = to_slint_button_style(cfg.button_style);
@ -63,10 +59,16 @@ pub fn run_power_menu(
ui.set_button_style(button_style);
ui.window()
.set_size(LogicalSize::new(cfg.screen_width as f32, cfg.screen_height as f32));
ui.window().set_size(LogicalSize::new(
cfg.screen_width as f32,
cfg.screen_height as f32,
));
ui.window().set_position(LogicalPosition::new(0.0, 0.0));
ui.on_request_close(|| {
slint::quit_event_loop();
});
ui.run();
Ok(())

View File

@ -71,6 +71,8 @@ component PowerMenuButton {
}
export component PowerMenu inherits Window {
callback request_close();
in property <int> screen_width;
in property <int> screen_height;
in property <color> screen_bg;
@ -113,6 +115,18 @@ export component PowerMenu inherits Window {
y:0;
background: screen_bg;
// Close upon clicking outside the menu
TouchArea {
x: 0;
y: 0;
width: 100%;
height: 100%;
clicked => {
request_close();
}
}
// Menu pop-up
Rectangle {
background: menu_bg;
@ -122,6 +136,10 @@ export component PowerMenu inherits Window {
x: menu_pos_x *1px;
y: menu_pos_y *1px;
TouchArea {
// Empty. Prevent close-on-click inside the menu.
}
// Buttons
HorizontalLayout {
padding: menu_padding_x *1px;