refactor: fix hardcoded values, remove println, remove doubles

This commit is contained in:
2026-08-26 15:16:09 +05:00
parent 4ff48bad0f
commit 03d4be8444
10 changed files with 513 additions and 415 deletions
+9 -7
View File
@@ -1,10 +1,12 @@
# Ревью кода bropicker (2026-08-26)
# Ревью кода bropicker (2026-08-26) — ВЫПОЛНЕНО
> Скоуп: src/main.rs (401 строка), src/config.rs (~210), src/winit.rs (45),
> ui/*.slint. Плюс вывод clippy.
>
> Вердикт: для MVP код здоровый, критических дыр нет. Но есть 2 мусорных
> артефакта правок, крупное дублирование и файл-бог. Ниже по приоритетам.
> Итог рефакторинга: main.rs 401→340 строк (и стал тонким по смыслу),
> появился launcher.rs, 12 юнит-тестов, tools/check.ps1 (fmt+clippy -D warnings
> +test) зелёный, дублирование запуска устранено, индекс из UI вместо поиска
> по имени, все P0-P2 закрыты. UI проверен скриншотом — идентичен.
## P0 — мусор и артефакты правок (чинить сразу)
@@ -74,9 +76,9 @@
## Порядок исполнения (одна сессия)
1. P0 целиком + прогон clippy --fix (P2 первая строка)
2. P1: launcher.rs + launch_and_remember + From-конвертации + индекс из UI
3. Юнит-тесты чистых функций + tools/check.ps1
4. P3 косметика по остаточному принципу
1. [x] P0 целиком + прогон clippy --fix (P2 первая строка)
2. [x] P1: launcher.rs + launch_and_remember + From-конвертации + индекс из UI
3. [x] Юнит-тесты чистых функций + tools/check.ps1
4. [x] P3 косметика: импорты, Theme.accent/danger токены
Каждый пункт — зелёный `tools\check.ps1` перед коммитом.
+66 -10
View File
@@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
@@ -17,8 +17,12 @@ pub fn log(msg: &str) {
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&path) {
let _ = writeln!(f, "[{ts}] {msg}");
if let Ok(mut f) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&path)
{
let _ = writeln!(f, "[{ts}] [bp] {msg}");
}
}
@@ -103,7 +107,7 @@ pub fn save(cfg: &Config) {
pub fn ensure_browsers(cfg: &mut Config) {
if cfg.browsers.is_empty() {
cfg.browsers = detect_browsers();
log(&format!("[bp] autodetected {} browsers", cfg.browsers.len()));
log(&format!("autodetected {} browsers", cfg.browsers.len()));
if !cfg.browsers.is_empty() {
save(cfg);
}
@@ -128,14 +132,13 @@ fn extract_exe(cmd: &str) -> Option<String> {
}
pub fn detect_browsers() -> Vec<BrowserEntry> {
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
use winreg::RegKey;
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
let mut out: Vec<BrowserEntry> = Vec::new();
for hive in [HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER] {
let Ok(clients) =
RegKey::predef(hive).open_subkey("SOFTWARE\\Clients\\StartMenuInternet")
let Ok(clients) = RegKey::predef(hive).open_subkey("SOFTWARE\\Clients\\StartMenuInternet")
else {
continue;
};
@@ -143,9 +146,7 @@ pub fn detect_browsers() -> Vec<BrowserEntry> {
let Ok(client) = clients.open_subkey(&key_name) else {
continue;
};
let friendly: String = client
.get_value("")
.unwrap_or_else(|_| key_name.clone());
let friendly: String = client.get_value("").unwrap_or_else(|_| key_name.clone());
let command: String = client
.open_subkey("shell\\open\\command")
.and_then(|k| k.get_value(""))
@@ -200,3 +201,58 @@ pub fn is_known_browser(name: &str) -> bool {
let n = name.to_lowercase();
KNOWN_ICONS.iter().any(|(kw, _)| n.contains(kw))
}
#[cfg(test)]
mod tests {
use super::{domain_of, extract_exe, is_known_browser};
#[test]
fn domain_basic() {
assert_eq!(domain_of("https://example.com/path?q=1"), "example.com");
assert_eq!(domain_of("http://example.com"), "example.com");
}
#[test]
fn domain_strips_www_and_port() {
assert_eq!(domain_of("https://www.example.com:8443/a"), "example.com");
}
#[test]
fn domain_without_scheme() {
assert_eq!(domain_of("example.com/x"), "example.com");
}
#[test]
fn domain_lowercases() {
assert_eq!(domain_of("https://EXAMPLE.com"), "example.com");
}
#[test]
fn extract_exe_quoted_with_args() {
assert_eq!(
extract_exe(r#""C:\Program Files\app.exe" --flag"#),
Some(r"C:\Program Files\app.exe".to_string())
);
}
#[test]
fn extract_exe_unquoted() {
assert_eq!(
extract_exe(r"C:\tools\app.exe --other"),
Some(r"C:\tools\app.exe".to_string())
);
}
#[test]
fn extract_exe_empty() {
assert_eq!(extract_exe(" "), None);
}
#[test]
fn known_browsers_match_case_insensitive() {
assert!(is_known_browser("Mozilla Firefox"));
assert!(is_known_browser("google chrome"));
assert!(is_known_browser("Яндекс Браузер"));
assert!(!is_known_browser("Zen Browser"));
}
}
+96
View File
@@ -0,0 +1,96 @@
use std::cell::RefCell;
use std::rc::Rc;
use crate::config::{self, BrowserEntry, Config};
pub fn split_flags(flags: &str) -> Vec<String> {
let mut out = Vec::new();
let mut cur = String::new();
let mut in_quotes = false;
for c in flags.chars() {
match c {
'"' => in_quotes = !in_quotes,
' ' if !in_quotes => {
if !cur.is_empty() {
out.push(std::mem::take(&mut cur));
}
}
_ => cur.push(c),
}
}
if !cur.is_empty() {
out.push(cur);
}
out
}
pub fn launch(entry: &BrowserEntry, url: &str) -> std::io::Result<()> {
let mut cmd = std::process::Command::new(&entry.path);
if !entry.flags.is_empty() {
cmd.args(split_flags(&entry.flags));
}
cmd.arg(url).spawn().map(|_| ())
}
/// Launch `entry` with `url` and, when `remember` is set, store the
/// domain→browser pair in the config. Returns `false` on spawn failure
/// (the error is logged).
pub fn launch_and_remember(
cfg: &Rc<RefCell<Config>>,
entry: &BrowserEntry,
url: &str,
remember: bool,
) -> bool {
config::log(&format!(
"[bp] launching {:?} {:?} {}",
entry.path, entry.flags, url
));
match launch(entry, url) {
Ok(_) => {
if remember {
let domain = config::domain_of(url);
if !domain.is_empty() {
cfg.borrow_mut()
.remembered
.insert(domain, entry.name.clone());
config::save(&cfg.borrow());
}
}
true
}
Err(e) => {
config::log(&format!("[bp] launch failed: {e}"));
false
}
}
}
#[cfg(test)]
mod tests {
use super::split_flags;
#[test]
fn empty_flags() {
assert!(split_flags("").is_empty());
assert!(split_flags(" ").is_empty());
}
#[test]
fn simple_flags() {
assert_eq!(split_flags("-P Work"), vec!["-P", "Work"]);
}
#[test]
fn quoted_spaces_stay_one_arg() {
assert_eq!(
split_flags(r#"--profile-directory="Profile 1""#),
vec!["--profile-directory=Profile 1"]
);
assert_eq!(split_flags(r#"-P "My Work""#), vec!["-P", "My Work"]);
}
#[test]
fn multiple_spaces_collapse() {
assert_eq!(split_flags(" a b "), vec!["a", "b"]);
}
}
+113 -188
View File
@@ -1,4 +1,7 @@
#![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")]
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
use std::cell::RefCell;
use std::rc::Rc;
@@ -7,43 +10,38 @@ use i_slint_backend_winit::WinitWindowAccessor;
use slint::Model;
mod config;
mod launcher;
mod winit;
use config::{BrowserEntry, Config};
slint::include_modules!();
fn split_flags(flags: &str) -> Vec<String> {
let mut out = Vec::new();
let mut cur = String::new();
let mut in_quotes = false;
for c in flags.chars() {
match c {
'"' => in_quotes = !in_quotes,
' ' if !in_quotes => {
if !cur.is_empty() {
out.push(std::mem::take(&mut cur));
}
}
_ => cur.push(c),
const DEFAULT_URL: &str = "ku6epxboctuk.github.io";
const ENV_THEME: &str = "BP_THEME";
const ENV_VIEW: &str = "BP_VIEW";
const VIEW_SETTINGS: &str = "settings";
impl From<&BrowserConfig> for BrowserEntry {
fn from(b: &BrowserConfig) -> Self {
Self {
name: b.name.to_string(),
path: b.path.to_string(),
flags: b.flags.to_string(),
icon: String::new(),
emoji: b.emoji.to_string(),
}
}
if !cur.is_empty() {
out.push(cur);
}
out
}
fn load_icon(rel: &str) -> slint::Image {
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
let p = dir.join(rel);
if p.exists() {
return slint::Image::load_from_path(&p).unwrap_or_default();
}
}
}
slint::Image::load_from_path(std::path::Path::new(rel)).unwrap_or_default()
let from_exe = std::env::current_exe()
.ok()
.and_then(|exe| exe.parent().map(|dir| dir.join(rel)));
let path = from_exe
.filter(|p| p.exists())
.unwrap_or_else(|| std::path::PathBuf::from(rel));
slint::Image::load_from_path(&path).unwrap_or_default()
}
fn to_ui_config(entry: &BrowserEntry) -> BrowserConfig {
@@ -65,14 +63,6 @@ fn to_ui_config(entry: &BrowserEntry) -> BrowserConfig {
}
}
fn launch(entry: &BrowserEntry, url: &str) -> std::io::Result<()> {
let mut cmd = std::process::Command::new(&entry.path);
if !entry.flags.is_empty() {
cmd.args(split_flags(&entry.flags));
}
cmd.arg(url).spawn().map(|_| ())
}
fn refresh_models(
browser_model: &slint::VecModel<BrowserConfig>,
remembered_model: &slint::VecModel<RememberedEntry>,
@@ -91,32 +81,63 @@ fn refresh_models(
remembered_model.set_vec(rows);
}
fn init(cfg: Config, url: Option<String>) -> State {
let cfg = Rc::new(RefCell::new(cfg));
fn launch_index(
main_window: &MainWindow,
browser_model: &slint::VecModel<BrowserConfig>,
cfg: &Rc<RefCell<Config>>,
index: usize,
) {
let Some(ui_entry) = browser_model.row_data(index) else {
return;
};
main_window.set_selected_index(index as i32);
let ui_browsers: Vec<BrowserConfig> = cfg
.borrow()
.browsers
.iter()
.map(to_ui_config)
.collect();
let browser_model = Rc::new(slint::VecModel::<BrowserConfig>::from(ui_browsers));
let entry = BrowserEntry::from(&ui_entry);
let url = main_window.get_current_url().to_string();
let remember = main_window.get_remember_choice();
if launcher::launch_and_remember(cfg, &entry, &url, remember) {
let _ = main_window.window().hide();
}
}
fn open_config_in_editor() {
let path = config::config_path();
if !path.exists() {
let mut c = config::load();
config::ensure_browsers(&mut c);
}
let editor = std::process::Command::new("subl")
.arg(&path)
.spawn()
.or_else(|_| std::process::Command::new("notepad").arg(&path).spawn());
if let Err(e) = editor {
config::log(&format!("cannot open editor: {e}"));
}
}
fn init(cfg: Config, url: Option<String>) -> (MainWindow, SettingsWindow) {
let cfg = Rc::new(RefCell::new(cfg));
let browser_model = Rc::new(slint::VecModel::<BrowserConfig>::from(vec![]));
let remembered_model = Rc::new(slint::VecModel::<RememberedEntry>::from(vec![]));
let main_window = MainWindow::new().unwrap();
let settings_window = SettingsWindow::new().unwrap();
if std::env::var("BP_THEME").as_deref() == Ok("light") {
if std::env::var(ENV_THEME).as_deref() == Ok("light") {
main_window.global::<Theme>().set_is_dark(false);
}
refresh_models(&browser_model, &remembered_model, &cfg.borrow());
let settings_window = SettingsWindow::new().unwrap();
settings_window.set_browsers(browser_model.clone().into());
settings_window.set_remembered(remembered_model.clone().into());
main_window.set_browser_model(browser_model.clone().into());
main_window.set_current_url(
url.clone().unwrap_or_else(|| "ku6epxboctuk.github.io".into()).into(),
url.clone()
.unwrap_or_else(|| DEFAULT_URL.to_string())
.into(),
);
if let Some(u) = &url {
main_window.set_current_domain(config::domain_of(u).into());
@@ -128,46 +149,8 @@ fn init(cfg: Config, url: Option<String>) -> State {
let main_window = main_window.clone_strong();
let browser_model = browser_model.clone();
let cfg = cfg.clone();
move |browser: BrowserConfig| {
let index = (0..browser_model.row_count())
.find(|&i| {
browser_model
.row_data(i)
.is_some_and(|b| b.name == browser.name)
})
.unwrap_or(0);
main_window.set_selected_index(index as i32);
let entry = browser_model.row_data(index).expect("row exists");
let url = main_window.get_current_url().to_string();
let entry_ref = BrowserEntry {
name: entry.name.to_string(),
path: entry.path.to_string(),
flags: entry.flags.to_string(),
icon: String::new(),
emoji: String::new(),
};
let url = main_window.get_current_url().to_string();
config::log(&format!(
"[bp] launching [{}]: {:?} {:?} {}",
index, entry_ref.path, entry_ref.flags, url
));
match launch(&entry_ref, &url) {
Ok(_) => {
let domain = config::domain_of(&url);
if main_window.get_remember_choice() && !domain.is_empty() {
cfg.borrow_mut()
.remembered
.insert(domain, entry_ref.name.clone());
config::save(&cfg.borrow());
}
let _ = main_window.window().hide();
}
Err(e) => config::log(&format!("[bp] launch failed: {e}")),
}
move |index: i32| {
launch_index(&main_window, &browser_model, &cfg, index.max(0) as usize);
}
});
@@ -177,36 +160,7 @@ fn init(cfg: Config, url: Option<String>) -> State {
let cfg = cfg.clone();
move || {
let index = main_window.get_selected_index().max(0) as usize;
let Some(ui_entry) = browser_model.row_data(index) else {
return;
};
let entry = BrowserEntry {
name: ui_entry.name.to_string(),
path: ui_entry.path.to_string(),
flags: ui_entry.flags.to_string(),
icon: String::new(),
emoji: String::new(),
};
let url = main_window.get_current_url().to_string();
config::log(&format!(
"[bp] launching [{}]: {:?} {:?} {}",
index, entry.path, entry.flags, url
));
match launch(&entry, &url) {
Ok(_) => {
let domain = config::domain_of(&url);
if main_window.get_remember_choice() && !domain.is_empty() {
cfg.borrow_mut()
.remembered
.insert(domain, entry.name.clone());
config::save(&cfg.borrow());
}
let _ = main_window.window().hide();
}
Err(e) => config::log(&format!("[bp] launch failed: {e}")),
}
launch_index(&main_window, &browser_model, &cfg, index);
}
});
@@ -227,7 +181,7 @@ fn init(cfg: Config, url: Option<String>) -> State {
});
main_window.on_settings_clicked(move || {
println!("Settings clicked");
open_config_in_editor();
});
main_window.on_request_drag({
@@ -239,20 +193,10 @@ fn init(cfg: Config, url: Option<String>) -> State {
}
});
main_window.on_settings_clicked({
main_window.on_esc_pressed({
let main_window = main_window.clone_strong();
move || {
let path = config::config_path();
if !path.exists() {
let mut c = config::load();
config::ensure_browsers(&mut c);
}
let editor = std::process::Command::new("subl")
.arg(&path)
.spawn()
.or_else(|_| std::process::Command::new("notepad").arg(&path).spawn());
if let Err(e) = editor {
config::log(&format!("[bp] cannot open editor: {e}"));
}
let _ = main_window.window().hide();
}
});
@@ -265,7 +209,6 @@ fn init(cfg: Config, url: Option<String>) -> State {
cfg.borrow_mut().browsers.retain(|b| b.name != name);
config::save(&cfg.borrow());
refresh_models(&browser_model, &remembered_model, &cfg.borrow());
println!("Removed browser: {name}");
}
});
@@ -278,7 +221,6 @@ fn init(cfg: Config, url: Option<String>) -> State {
cfg.borrow_mut().remembered.remove(&domain);
config::save(&cfg.borrow());
refresh_models(&browser_model, &remembered_model, &cfg.borrow());
println!("Removed remembered domain: {domain}");
}
});
@@ -301,7 +243,6 @@ fn init(cfg: Config, url: Option<String>) -> State {
config::save(&cfg_ref);
drop(cfg_ref);
refresh_models(&browser_model, &remembered_model, &cfg.borrow());
println!("Re-detected browsers");
}
});
@@ -314,13 +255,6 @@ fn init(cfg: Config, url: Option<String>) -> State {
}
});
main_window.on_esc_pressed({
let main_window = main_window.clone_strong();
move || {
let _ = main_window.window().hide();
}
});
settings_window.window().on_close_requested({
let main_window = main_window.clone_strong();
move || {
@@ -329,22 +263,39 @@ fn init(cfg: Config, url: Option<String>) -> State {
}
});
main_window.set_browser_model(browser_model.clone().into());
State {
main_window,
settings_window,
browser_model,
remembered_model,
config: cfg,
}
(main_window, settings_window)
}
pub struct State {
pub main_window: MainWindow,
pub settings_window: SettingsWindow,
pub browser_model: Rc<slint::VecModel<BrowserConfig>>,
pub remembered_model: Rc<slint::VecModel<RememberedEntry>>,
pub config: Rc<RefCell<Config>>,
fn show_and_run(
main_window: MainWindow,
settings_window: SettingsWindow,
start: std::time::Instant,
) {
let settings_only = std::env::var(ENV_VIEW).as_deref() == Ok(VIEW_SETTINGS);
if settings_only {
settings_window.show().unwrap();
let weak = settings_window.as_weak();
let _ = slint::invoke_from_event_loop(move || {
if let Some(sw) = weak.upgrade() {
winit::center_window(sw.window());
sw.invoke_restore_focus();
}
config::log(&format!("cold start -> event loop: {:?}", start.elapsed()));
});
settings_window.run().unwrap();
} else {
main_window.show().unwrap();
let weak = main_window.as_weak();
let _ = slint::invoke_from_event_loop(move || {
if let Some(mw) = weak.upgrade() {
winit::center_window(mw.window());
mw.invoke_restore_focus();
}
config::log(&format!("cold start -> event loop: {:?}", start.elapsed()));
});
main_window.run().unwrap();
}
}
pub fn main() {
@@ -359,43 +310,17 @@ pub fn main() {
let domain = config::domain_of(url);
if let Some(name) = cfg.remembered.get(&domain).cloned() {
if cfg.settings.always_ask {
config::log(&format!("[bp] '{domain}' remembered -> {name}, but always_ask is on"));
config::log(&format!(
"'{domain}' remembered -> {name}, but always_ask is on"
));
} else if let Some(entry) = cfg.browsers.iter().find(|b| b.name == name) {
config::log(&format!("[bp] '{domain}' remembered -> launching {name}"));
let _ = launch(entry, url);
config::log(&format!("'{domain}' remembered -> launching {name}"));
let _ = launcher::launch(entry, url);
return;
}
}
}
let state = init(cfg, url);
if std::env::var("BP_VIEW").as_deref() == Ok("settings") {
state.settings_window.show().unwrap();
let weak = state.settings_window.as_weak();
let _ = slint::invoke_from_event_loop(move || {
if let Some(sw) = weak.upgrade() {
winit::center_window(sw.window());
sw.invoke_restore_focus();
}
});
state.settings_window.run().unwrap();
} else {
let main_window = state.main_window.clone_strong();
main_window.show().unwrap();
let weak = main_window.as_weak();
let _ = slint::invoke_from_event_loop(move || {
if let Some(mw) = weak.upgrade() {
winit::center_window(mw.window());
mw.invoke_restore_focus();
}
config::log(&format!(
"[bp] cold start -> event loop: {:?}",
start.elapsed()
));
});
state.main_window.run().unwrap();
}
let (main_window, settings_window) = init(cfg, url);
show_and_run(main_window, settings_window, start);
}
+3 -4
View File
@@ -6,10 +6,9 @@ use i_slint_backend_winit::winit::window::Window;
pub fn center_window(window: &slint::Window) {
if window.has_winit_window() {
window.with_winit_window(|window: &Window| {
match window.current_monitor() {
Some(monitor) => set_centered(window, &monitor),
None => (),
};
if let Some(monitor) = window.current_monitor() {
set_centered(window, &monitor);
}
None as Option<()>
});
+12
View File
@@ -0,0 +1,12 @@
$ErrorActionPreference = "Stop"
cargo fmt
if ($LASTEXITCODE -ne 0) { exit 1 }
cargo clippy --all-targets -- -D warnings
if ($LASTEXITCODE -ne 0) { exit 1 }
cargo test
if ($LASTEXITCODE -ne 0) { exit 1 }
Write-Host "check OK"
+6 -5
View File
@@ -1,13 +1,14 @@
import { Theme } from "./theme.slint";
export component BrowserItem inherits Rectangle {
in property <int> index;
in property <image> browser-icon;
in property <string> emoji;
in property <bool> icon-mask;
in property <string> browser-name;
in property <bool> is-default;
in property <bool> selected;
callback clicked;
callback clicked(int);
height: 84px;
background: transparent;
@@ -20,12 +21,12 @@ export component BrowserItem inherits Rectangle {
: root.selected ? Theme.surface-elevated
: Theme.surface-item;
border-width: root.selected ? 2px : 1px;
border-color: root.selected ? #3b82f680 : transparent;
border-color: root.selected ? Theme.accent-ring : transparent;
touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.clicked();
root.clicked(root.index);
}
}
@@ -84,7 +85,7 @@ export component BrowserItem inherits Rectangle {
if root.is-default: Rectangle {
height: 22px;
border-radius: 11px;
background: #3b82f620;
background: Theme.accent-soft;
HorizontalLayout {
padding-left: 10px;
@@ -109,7 +110,7 @@ export component BrowserItem inherits Rectangle {
width: 16px;
height: 16px;
source: @image-url("../icons/check.svg");
colorize: #3b82f6;
colorize: Theme.accent;
visible: root.selected;
}
+7 -6
View File
@@ -1,4 +1,4 @@
import { Button, CheckBox, ListView } from "std-widgets.slint";
import { ListView } from "std-widgets.slint";
import { BrowserItem } from "./browser_item.slint";
import { Header } from "./header.slint";
import { Footer } from "./footer.slint";
@@ -32,9 +32,9 @@ component CheckRow inherits Rectangle {
Rectangle {
width: 20px;
border-radius: 6px;
background: root.checked ? #3b82f6 : transparent;
background: root.checked ? Theme.accent : transparent;
border-width: 2px;
border-color: root.checked ? #3b82f6 : Theme.border-main;
border-color: root.checked ? Theme.accent : Theme.border-main;
Image {
x: (parent.width - self.width) / 2;
@@ -65,7 +65,7 @@ export component MainWindow inherits Window {
in-out property <bool> always-ask: true;
in property <int> selected-index: 0;
callback launch-browser(BrowserConfig);
callback launch-browser(int);
callback toggle-remember(bool);
callback toggle-always-ask(bool);
callback settings-clicked();
@@ -128,14 +128,15 @@ export component MainWindow inherits Window {
vertical-stretch: 1;
for browser[i] in root.browser-model: BrowserItem {
index: i;
browser-icon: browser.icon;
emoji: browser.emoji;
icon-mask: browser.icon-mask;
browser-name: browser.name;
is-default: browser.is-default;
selected: i == root.selected-index;
clicked => {
root.launch-browser(browser);
clicked(idx) => {
root.launch-browser(idx);
}
}
}
+4 -4
View File
@@ -137,7 +137,7 @@ export component SettingsWindow inherits Window {
Rectangle {
width: 30px;
border-radius: 6px;
background: del-touch.has-hover ? #ef444420 : transparent;
background: del-touch.has-hover ? Theme.danger-soft : transparent;
del-touch := TouchArea {
mouse-cursor: pointer;
@@ -152,7 +152,7 @@ export component SettingsWindow inherits Window {
width: 16px;
height: 16px;
source: @image-url("../icons/trash.svg");
colorize: del-touch.has-hover ? #ef4444 : Theme.text-muted;
colorize: del-touch.has-hover ? Theme.danger : Theme.text-muted;
}
}
}
@@ -209,7 +209,7 @@ export component SettingsWindow inherits Window {
Rectangle {
width: 30px;
border-radius: 6px;
background: del2-touch.has-hover ? #ef444420 : transparent;
background: del2-touch.has-hover ? Theme.danger-soft : transparent;
del2-touch := TouchArea {
mouse-cursor: pointer;
@@ -224,7 +224,7 @@ export component SettingsWindow inherits Window {
width: 16px;
height: 16px;
source: @image-url("../icons/trash.svg");
colorize: del2-touch.has-hover ? #ef4444 : Theme.text-muted;
colorize: del2-touch.has-hover ? Theme.danger : Theme.text-muted;
}
}
}
+6
View File
@@ -21,6 +21,12 @@ export global Theme {
out property <brush> text-hint: is-dark ? #475569 : #475569;
out property <color> accent: #3b82f6;
out property <color> accent-ring: #3b82f680;
out property <color> accent-soft: #3b82f620;
out property <color> danger: #ef4444;
out property <color> danger-soft: #ef444420;
out property <brush> shadow-color: is-dark ? #3b82f640 : #3b82f640;
out property <length> shadow-primary-x: 0px;