feat: add setting variant

This commit is contained in:
2026-08-26 07:37:06 +05:00
parent c7557c163a
commit fc5a7638bc
7 changed files with 382 additions and 7 deletions
+7 -4
View File
@@ -116,11 +116,14 @@ HTML-макеты → PNG: **headless Edge** (`--headless --screenshot`), он
домена в конфиг; переключение галочек персистится домена в конфиг; переключение галочек персистится
- [x] URL-бар привязан к свойству (был захардкожен), метка чекбокса — домен - [x] URL-бар привязан к свойству (был захардкожен), метка чекбокса — домен
### E2 — страница настроек (следующий шаг) ### E2 — страница настроек — ГОТОВО (базовая версия)
- [ ] Переключение вид/настройки по кнопке cog (settings-clicked) - [x] Переключение вид/настройки по кнопке cog; Esc в настройках = назад;
- [ ] Редактор списка браузеров: имя/путь/флаги (LineEdit), вкл/выкл env `BP_VIEW=settings` для скриншотов
- [ ] Список запомненных доменов с удалением - [x] Список браузеров: иконка + имя + путь (elide), удаление корзиной
- [x] Кнопка «Автодетект»: мерж найденных по пути, без дублей
- [x] Запомненные домены (domain → browser) с удалением
- [ ] Редактирование браузера (имя/путь/флаги через LineEdit) — следующий шаг
- [ ] Чтение флагов из .lnk ярлыков (крейс `lnk`) — по желанию - [ ] Чтение флагов из .lnk ярлыков (крейс `lnk`) — по желанию
- [ ] Добавить логотипы Edge/Zen в logos/ (пересоздать набор из alrrr/browser-logos) - [ ] Добавить логотипы Edge/Zen в logos/ (пересоздать набор из alrrr/browser-logos)
+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m12 19-7-7 7-7" />
<path d="M19 12H5" />
</svg>

After

Width:  |  Height:  |  Size: 215 B

+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 6h18" />
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" />
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
</svg>

After

Width:  |  Height:  |  Size: 294 B

+100 -1
View File
@@ -38,6 +38,24 @@ fn launch(entry: &BrowserEntry, url: &str) -> std::io::Result<()> {
cmd.arg(url).spawn().map(|_| ()) cmd.arg(url).spawn().map(|_| ())
} }
fn refresh_models(
browser_model: &slint::VecModel<BrowserConfig>,
remembered_model: &slint::VecModel<RememberedEntry>,
cfg: &Config,
) {
let rows: Vec<BrowserConfig> = cfg.browsers.iter().map(to_ui_config).collect();
browser_model.set_vec(rows);
let rows: Vec<RememberedEntry> = cfg
.remembered
.iter()
.map(|(d, b)| RememberedEntry {
domain: d.clone().into(),
browser: b.clone().into(),
})
.collect();
remembered_model.set_vec(rows);
}
fn init(cfg: Config, url: Option<String>) -> State { fn init(cfg: Config, url: Option<String>) -> State {
let cfg = Rc::new(RefCell::new(cfg)); let cfg = Rc::new(RefCell::new(cfg));
@@ -48,6 +66,7 @@ fn init(cfg: Config, url: Option<String>) -> State {
.map(to_ui_config) .map(to_ui_config)
.collect(); .collect();
let browser_model = Rc::new(slint::VecModel::<BrowserConfig>::from(ui_browsers)); let browser_model = Rc::new(slint::VecModel::<BrowserConfig>::from(ui_browsers));
let remembered_model = Rc::new(slint::VecModel::<RememberedEntry>::from(vec![]));
let main_window = MainWindow::new().unwrap(); let main_window = MainWindow::new().unwrap();
@@ -55,6 +74,12 @@ fn init(cfg: Config, url: Option<String>) -> State {
main_window.global::<Theme>().set_is_dark(false); main_window.global::<Theme>().set_is_dark(false);
} }
refresh_models(&browser_model, &remembered_model, &cfg.borrow());
if std::env::var("BP_VIEW").as_deref() == Ok("settings") {
main_window.set_settings_open(true);
}
main_window.set_current_url( main_window.set_current_url(
url.clone().unwrap_or_else(|| "ku6epxboctuk.github.io".into()).into(), url.clone().unwrap_or_else(|| "ku6epxboctuk.github.io".into()).into(),
); );
@@ -176,17 +201,90 @@ fn init(cfg: Config, url: Option<String>) -> State {
} }
}); });
main_window.on_settings_clicked({
let main_window = main_window.clone_strong();
let browser_model = browser_model.clone();
let remembered_model = remembered_model.clone();
let cfg = cfg.clone();
move || {
refresh_models(&browser_model, &remembered_model, &cfg.borrow());
main_window.set_settings_open(true);
}
});
main_window.on_settings_closed({
let main_window = main_window.clone_strong();
move || {
main_window.set_settings_open(false);
}
});
main_window.on_remove_browser({
let browser_model = browser_model.clone();
let remembered_model = remembered_model.clone();
let cfg = cfg.clone();
move |name| {
let name = name.to_string();
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}");
}
});
main_window.on_remove_remembered({
let browser_model = browser_model.clone();
let remembered_model = remembered_model.clone();
let cfg = cfg.clone();
move |domain| {
let domain = domain.to_string();
cfg.borrow_mut().remembered.remove(&domain);
config::save(&cfg.borrow());
refresh_models(&browser_model, &remembered_model, &cfg.borrow());
println!("Removed remembered domain: {domain}");
}
});
main_window.on_redetect_browsers({
let browser_model = browser_model.clone();
let remembered_model = remembered_model.clone();
let cfg = cfg.clone();
move || {
let detected = config::detect_browsers();
let mut cfg_ref = cfg.borrow_mut();
for entry in detected {
if !cfg_ref
.browsers
.iter()
.any(|b| b.path.eq_ignore_ascii_case(&entry.path))
{
cfg_ref.browsers.push(entry);
}
}
config::save(&cfg_ref);
drop(cfg_ref);
refresh_models(&browser_model, &remembered_model, &cfg.borrow());
println!("Re-detected browsers");
}
});
main_window.on_esc_pressed({ main_window.on_esc_pressed({
let main_window = main_window.clone_strong(); let main_window = main_window.clone_strong();
move || { move || {
let _ = main_window.window().hide(); if main_window.get_settings_open() {
main_window.set_settings_open(false);
} else {
let _ = main_window.window().hide();
}
} }
}); });
main_window.set_browser_model(browser_model.clone().into()); main_window.set_browser_model(browser_model.clone().into());
main_window.set_remembered_list(remembered_model.clone().into());
State { State {
main_window, main_window,
browser_model, browser_model,
remembered_model,
config: cfg, config: cfg,
} }
} }
@@ -194,6 +292,7 @@ fn init(cfg: Config, url: Option<String>) -> State {
pub struct State { pub struct State {
pub main_window: MainWindow, pub main_window: MainWindow,
pub browser_model: Rc<slint::VecModel<BrowserConfig>>, pub browser_model: Rc<slint::VecModel<BrowserConfig>>,
pub remembered_model: Rc<slint::VecModel<RememberedEntry>>,
pub config: Rc<RefCell<Config>>, pub config: Rc<RefCell<Config>>,
} }
+26 -2
View File
@@ -2,7 +2,8 @@ import { Button, CheckBox, ListView } from "std-widgets.slint";
import { BrowserItem } from "./browser_item.slint"; import { BrowserItem } from "./browser_item.slint";
import { Header } from "./header.slint"; import { Header } from "./header.slint";
import { Footer } from "./footer.slint"; import { Footer } from "./footer.slint";
import { BrowserConfig } from "./types.slint"; import { SettingsView } from "./settings.slint";
import { BrowserConfig, RememberedEntry } from "./types.slint";
import { Theme } from "theme.slint"; import { Theme } from "theme.slint";
export { Theme } export { Theme }
@@ -62,11 +63,17 @@ export component MainWindow inherits Window {
in-out property <bool> remember-choice: false; in-out property <bool> remember-choice: false;
in-out property <bool> always-ask: true; in-out property <bool> always-ask: true;
in property <int> selected-index: 0; in property <int> selected-index: 0;
in property <[RememberedEntry]> remembered-list: [];
in-out property <bool> settings-open: false;
callback launch-browser(BrowserConfig); callback launch-browser(BrowserConfig);
callback toggle-remember(bool); callback toggle-remember(bool);
callback toggle-always-ask(bool); callback toggle-always-ask(bool);
callback settings-clicked(); callback settings-clicked();
callback settings-closed();
callback remove-browser(string);
callback remove-remembered(string);
callback redetect-browsers();
callback open-clicked(); callback open-clicked();
callback request-drag(); callback request-drag();
callback esc-pressed(); callback esc-pressed();
@@ -106,7 +113,24 @@ export component MainWindow inherits Window {
border-width: 1px; border-width: 1px;
border-color: Theme.border-main; border-color: Theme.border-main;
VerticalLayout { if root.settings-open: SettingsView {
browsers: root.browser-model;
remembered: root.remembered-list;
back => {
root.settings-closed();
}
remove-browser(name) => {
root.remove-browser(name);
}
remove-remembered(domain) => {
root.remove-remembered(domain);
}
redetect => {
root.redetect-browsers();
}
}
if !root.settings-open: VerticalLayout {
Header { Header {
url: current-url; url: current-url;
+235
View File
@@ -0,0 +1,235 @@
import { ListView } from "std-widgets.slint";
import { Theme } from "./theme.slint";
import { BrowserConfig, RememberedEntry } from "./types.slint";
export component SettingsView inherits Rectangle {
in property <[BrowserConfig]> browsers;
in property <[RememberedEntry]> remembered;
callback back();
callback remove-browser(string);
callback remove-remembered(string);
callback redetect();
background: transparent;
VerticalLayout {
padding: 16px;
spacing: 12px;
HorizontalLayout {
spacing: 8px;
Rectangle {
width: 32px;
border-radius: 8px;
background: back-touch.has-hover ? Theme.surface-hover : transparent;
back-touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.back();
}
}
Image {
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
width: 20px;
height: 20px;
source: @image-url("../icons/arrow_left.svg");
colorize: back-touch.has-hover ? Theme.text-main : Theme.text-muted;
}
}
VerticalLayout {
alignment: center;
Text {
text: "Настройки";
color: Theme.text-main;
font-size: 18px;
font-weight: 600;
vertical-alignment: center;
}
}
}
HorizontalLayout {
spacing: 8px;
Text {
text: "Браузеры";
color: Theme.text-muted;
font-size: 13px;
vertical-alignment: center;
}
Rectangle {}
Rectangle {
width: 120px;
height: 26px;
border-radius: 8px;
background: redetect-touch.has-hover ? Theme.surface-hover : Theme.surface-elevated;
border-width: 1px;
border-color: Theme.border-main;
redetect-touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.redetect();
}
}
Text {
width: 100%;
height: 100%;
text: "Автодетект";
color: redetect-touch.has-hover ? Theme.text-main : Theme.text-control;
font-size: 12px;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
Rectangle {
vertical-stretch: 3;
border-radius: 8px;
background: Theme.surface-elevated;
clip: true;
ListView {
for b in root.browsers: Rectangle {
height: 52px;
background: transparent;
HorizontalLayout {
padding-left: 12px;
padding-right: 8px;
spacing: 10px;
Image {
width: 22px;
y: (parent.height - self.height) / 2;
source: b.icon;
}
VerticalLayout {
horizontal-stretch: 1;
alignment: center;
Text {
text: b.name;
color: Theme.text-main;
font-size: 14px;
}
Text {
text: b.path;
color: Theme.text-hint;
font-size: 11px;
font-family: "Consolas";
overflow: elide;
}
}
Rectangle {
width: 28px;
border-radius: 6px;
background: del-touch.has-hover ? #ef444420 : transparent;
del-touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.remove-browser(b.name);
}
}
Image {
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
width: 15px;
height: 15px;
source: @image-url("../icons/trash.svg");
colorize: del-touch.has-hover ? #ef4444 : Theme.text-muted;
}
}
}
}
}
}
Text {
text: "Запомненные домены";
color: Theme.text-muted;
font-size: 13px;
}
Rectangle {
vertical-stretch: 2;
border-radius: 8px;
background: Theme.surface-elevated;
clip: true;
ListView {
for r in root.remembered: Rectangle {
height: 40px;
background: transparent;
HorizontalLayout {
padding-left: 12px;
padding-right: 8px;
spacing: 10px;
VerticalLayout {
horizontal-stretch: 1;
alignment: center;
HorizontalLayout {
spacing: 6px;
Text {
text: r.domain;
color: Theme.text-main;
font-size: 13px;
font-family: "Consolas";
vertical-alignment: center;
}
Text {
text: "→ " + r.browser;
color: Theme.text-hint;
font-size: 12px;
vertical-alignment: center;
}
}
}
Rectangle {
width: 28px;
border-radius: 6px;
background: del2-touch.has-hover ? #ef444420 : transparent;
del2-touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.remove-remembered(r.domain);
}
}
Image {
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
width: 15px;
height: 15px;
source: @image-url("../icons/trash.svg");
colorize: del2-touch.has-hover ? #ef4444 : Theme.text-muted;
}
}
}
}
}
}
}
}
+5
View File
@@ -5,3 +5,8 @@ export struct BrowserConfig {
flags: string, flags: string,
is-default: bool, is-default: bool,
} }
export struct RememberedEntry {
domain: string,
browser: string,
}