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

View File

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

View File

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