feat: settings as separate window

This commit is contained in:
2026-08-26 09:17:28 +05:00
parent fc5a7638bc
commit 9534c6b1e3
4 changed files with 277 additions and 262 deletions
+12 -6
View File
@@ -116,13 +116,19 @@ HTML-макеты → PNG: **headless Edge** (`--headless --screenshot`), он
домена в конфиг; переключение галочек персистится домена в конфиг; переключение галочек персистится
- [x] URL-бар привязан к свойству (был захардкожен), метка чекбокса — домен - [x] URL-бар привязан к свойству (был захардкожен), метка чекбокса — домен
### E2 — страница настроек — ГОТОВО (базовая версия) ### E2 — страница настроек — ГОТОВО (отдельное окно)
- [x] Переключение вид/настройки по кнопке cog; Esc в настройках = назад; - [x] Настройки = **отдельное окно** `SettingsWindow` (760×560, обычная рамка,
env `BP_VIEW=settings` для скриншотов resizable; в маленьком пикере редактировать неудобно). То же событийное
- [x] Список браузеров: иконка + имя + путь (elide), удаление корзиной кольцо; Theme-глобал красит оба окна; модели шарятся (ModelRc один на оба)
- [x] Кнопка «Автодетект»: мерж найденных по пути, без дублей - [x] cog в пикере → показать окно (центрирование через invoke_from_event_loop —
- [x] Запомненные домены (domain → browser) с удалением winit-окно реализуется лениво, как и у пикера)
- [x] Две панели: браузеры (иконка/имя/путь/корзина) + запомненные домены
(domain → browser/корзина); кнопка «Автодетект» (мерж по пути)
- [x] Esc в настройках = закрыть окно; X работает по умолчанию
- [x] env `BP_VIEW=settings` — запуск сразу с окном настроек (без пикера);
нюанс: `run()` авто-показывает своё окно, поэтому в этом режиме цикл
крутится на settings_window
- [ ] Редактирование браузера (имя/путь/флаги через LineEdit) — следующий шаг - [ ] Редактирование браузера (имя/путь/флаги через LineEdit) — следующий шаг
- [ ] Чтение флагов из .lnk ярлыков (крейс `lnk`) — по желанию - [ ] Чтение флагов из .lnk ярлыков (крейс `lnk`) — по желанию
- [ ] Добавить логотипы Edge/Zen в logos/ (пересоздать набор из alrrr/browser-logos) - [ ] Добавить логотипы Edge/Zen в logos/ (пересоздать набор из alrrr/browser-logos)
+48 -24
View File
@@ -76,9 +76,9 @@ fn init(cfg: Config, url: Option<String>) -> State {
refresh_models(&browser_model, &remembered_model, &cfg.borrow()); refresh_models(&browser_model, &remembered_model, &cfg.borrow());
if std::env::var("BP_VIEW").as_deref() == Ok("settings") { let settings_window = SettingsWindow::new().unwrap();
main_window.set_settings_open(true); settings_window.set_browsers(browser_model.clone().into());
} settings_window.set_remembered(remembered_model.clone().into());
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(),
@@ -202,24 +202,20 @@ fn init(cfg: Config, url: Option<String>) -> State {
}); });
main_window.on_settings_clicked({ main_window.on_settings_clicked({
let main_window = main_window.clone_strong(); let settings_window = settings_window.clone_strong();
let browser_model = browser_model.clone();
let remembered_model = remembered_model.clone();
let cfg = cfg.clone();
move || { move || {
refresh_models(&browser_model, &remembered_model, &cfg.borrow()); settings_window.show().unwrap();
main_window.set_settings_open(true); 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();
}
});
} }
}); });
main_window.on_settings_closed({ settings_window.on_remove_browser({
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 browser_model = browser_model.clone();
let remembered_model = remembered_model.clone(); let remembered_model = remembered_model.clone();
let cfg = cfg.clone(); let cfg = cfg.clone();
@@ -232,7 +228,7 @@ fn init(cfg: Config, url: Option<String>) -> State {
} }
}); });
main_window.on_remove_remembered({ settings_window.on_remove_remembered({
let browser_model = browser_model.clone(); let browser_model = browser_model.clone();
let remembered_model = remembered_model.clone(); let remembered_model = remembered_model.clone();
let cfg = cfg.clone(); let cfg = cfg.clone();
@@ -245,7 +241,7 @@ fn init(cfg: Config, url: Option<String>) -> State {
} }
}); });
main_window.on_redetect_browsers({ settings_window.on_redetect({
let browser_model = browser_model.clone(); let browser_model = browser_model.clone();
let remembered_model = remembered_model.clone(); let remembered_model = remembered_model.clone();
let cfg = cfg.clone(); let cfg = cfg.clone();
@@ -268,21 +264,34 @@ fn init(cfg: Config, url: Option<String>) -> State {
} }
}); });
settings_window.on_esc_pressed({
let settings_window = settings_window.clone_strong();
let main_window = main_window.clone_strong();
move || {
let _ = settings_window.window().hide();
main_window.invoke_restore_focus();
}
});
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 || {
if main_window.get_settings_open() {
main_window.set_settings_open(false);
} else {
let _ = main_window.window().hide(); let _ = main_window.window().hide();
} }
});
settings_window.window().on_close_requested({
let main_window = main_window.clone_strong();
move || {
main_window.invoke_restore_focus();
slint::CloseRequestResponse::HideWindow
} }
}); });
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,
settings_window,
browser_model, browser_model,
remembered_model, remembered_model,
config: cfg, config: cfg,
@@ -291,6 +300,7 @@ fn init(cfg: Config, url: Option<String>) -> State {
pub struct State { pub struct State {
pub main_window: MainWindow, pub main_window: MainWindow,
pub settings_window: SettingsWindow,
pub browser_model: Rc<slint::VecModel<BrowserConfig>>, pub browser_model: Rc<slint::VecModel<BrowserConfig>>,
pub remembered_model: Rc<slint::VecModel<RememberedEntry>>, pub remembered_model: Rc<slint::VecModel<RememberedEntry>>,
pub config: Rc<RefCell<Config>>, pub config: Rc<RefCell<Config>>,
@@ -316,6 +326,18 @@ pub fn main() {
} }
let state = init(cfg, url); 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(); let main_window = state.main_window.clone_strong();
main_window.show().unwrap(); main_window.show().unwrap();
@@ -323,8 +345,10 @@ pub fn main() {
let _ = slint::invoke_from_event_loop(move || { let _ = slint::invoke_from_event_loop(move || {
if let Some(mw) = weak.upgrade() { if let Some(mw) = weak.upgrade() {
winit::center_window(mw.window()); winit::center_window(mw.window());
mw.invoke_restore_focus();
} }
}); });
main_window.run().unwrap(); state.main_window.run().unwrap();
}
} }
+12 -29
View File
@@ -2,11 +2,12 @@ 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 { SettingsView } from "./settings.slint"; import { BrowserConfig } from "./types.slint";
import { BrowserConfig, RememberedEntry } from "./types.slint";
import { Theme } from "theme.slint"; import { Theme } from "theme.slint";
export { Theme } export { Theme }
export { SettingsWindow } from "./settings.slint";
export { RememberedEntry } from "./types.slint";
component CheckRow inherits Rectangle { component CheckRow inherits Rectangle {
in property <string> label; in property <string> label;
@@ -63,17 +64,11 @@ 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();
@@ -83,9 +78,10 @@ export component MainWindow inherits Window {
width: 488px; width: 488px;
height: 800px; height: 800px;
default-font-family: "Segoe UI"; default-font-family: "Segoe UI";
scope := FocusScope {
FocusScope { init => {
init => { self.focus(); } self.focus();
}
key-pressed(event) => { key-pressed(event) => {
if (event.text == Key.Escape) { if (event.text == Key.Escape) {
root.esc-pressed(); root.esc-pressed();
@@ -96,6 +92,10 @@ export component MainWindow inherits Window {
} }
} }
callback restore-focus();
restore-focus => {
scope.focus();
}
VerticalLayout { VerticalLayout {
alignment: start; alignment: start;
width: 488px; width: 488px;
@@ -113,24 +113,7 @@ export component MainWindow inherits Window {
border-width: 1px; border-width: 1px;
border-color: Theme.border-main; border-color: Theme.border-main;
if root.settings-open: SettingsView { VerticalLayout {
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;
+62 -60
View File
@@ -2,58 +2,56 @@ import { ListView } from "std-widgets.slint";
import { Theme } from "./theme.slint"; import { Theme } from "./theme.slint";
import { BrowserConfig, RememberedEntry } from "./types.slint"; import { BrowserConfig, RememberedEntry } from "./types.slint";
export component SettingsView inherits Rectangle { export component SettingsWindow inherits Window {
title: "bropicker — настройки";
width: 760px;
height: 560px;
background: Theme.surface-main;
default-font-family: "Segoe UI";
in property <[BrowserConfig]> browsers; in property <[BrowserConfig]> browsers;
in property <[RememberedEntry]> remembered; in property <[RememberedEntry]> remembered;
callback back();
callback remove-browser(string); callback remove-browser(string);
callback remove-remembered(string); callback remove-remembered(string);
callback redetect(); callback redetect();
callback esc-pressed();
background: transparent; scope := FocusScope {
init => {
VerticalLayout { self.focus();
padding: 16px; }
spacing: 12px; key-pressed(event) => {
if (event.text == Key.Escape) {
HorizontalLayout { root.esc-pressed();
spacing: 8px; accept
} else {
Rectangle { reject
width: 32px; }
border-radius: 8px;
background: back-touch.has-hover ? Theme.surface-hover : transparent;
back-touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.back();
} }
} }
Image { callback restore-focus();
x: (parent.width - self.width) / 2; restore-focus => {
y: (parent.height - self.height) / 2; scope.focus();
width: 20px;
height: 20px;
source: @image-url("../icons/arrow_left.svg");
colorize: back-touch.has-hover ? Theme.text-main : Theme.text-muted;
}
} }
VerticalLayout { VerticalLayout {
alignment: center; padding: 20px;
spacing: 14px;
Text { Text {
text: "Настройки"; text: "Настройки";
color: Theme.text-main; color: Theme.text-main;
font-size: 18px; font-size: 20px;
font-weight: 600; font-weight: 600;
vertical-alignment: center;
}
}
} }
HorizontalLayout {
spacing: 14px;
VerticalLayout {
spacing: 8px;
HorizontalLayout { HorizontalLayout {
spacing: 8px; spacing: 8px;
@@ -67,7 +65,7 @@ export component SettingsView inherits Rectangle {
Rectangle {} Rectangle {}
Rectangle { Rectangle {
width: 120px; width: 110px;
height: 26px; height: 26px;
border-radius: 8px; border-radius: 8px;
background: redetect-touch.has-hover ? Theme.surface-hover : Theme.surface-elevated; background: redetect-touch.has-hover ? Theme.surface-hover : Theme.surface-elevated;
@@ -94,23 +92,25 @@ export component SettingsView inherits Rectangle {
} }
Rectangle { Rectangle {
vertical-stretch: 3; vertical-stretch: 1;
border-radius: 8px; border-radius: 12px;
background: Theme.surface-elevated; background: Theme.surface-elevated;
border-width: 1px;
border-color: Theme.border-main;
clip: true; clip: true;
ListView { ListView {
for b in root.browsers: Rectangle { for b in root.browsers: Rectangle {
height: 52px; height: 56px;
background: transparent; background: transparent;
HorizontalLayout { HorizontalLayout {
padding-left: 12px; padding-left: 14px;
padding-right: 8px; padding-right: 10px;
spacing: 10px; spacing: 12px;
Image { Image {
width: 22px; width: 26px;
y: (parent.height - self.height) / 2; y: (parent.height - self.height) / 2;
source: b.icon; source: b.icon;
} }
@@ -135,7 +135,7 @@ export component SettingsView inherits Rectangle {
} }
Rectangle { Rectangle {
width: 28px; width: 30px;
border-radius: 6px; border-radius: 6px;
background: del-touch.has-hover ? #ef444420 : transparent; background: del-touch.has-hover ? #ef444420 : transparent;
@@ -149,8 +149,8 @@ export component SettingsView inherits Rectangle {
Image { Image {
x: (parent.width - self.width) / 2; x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2; y: (parent.height - self.height) / 2;
width: 15px; width: 16px;
height: 15px; height: 16px;
source: @image-url("../icons/trash.svg"); source: @image-url("../icons/trash.svg");
colorize: del-touch.has-hover ? #ef4444 : Theme.text-muted; colorize: del-touch.has-hover ? #ef4444 : Theme.text-muted;
} }
@@ -159,6 +159,10 @@ export component SettingsView inherits Rectangle {
} }
} }
} }
}
VerticalLayout {
spacing: 8px;
Text { Text {
text: "Запомненные домены"; text: "Запомненные домены";
@@ -167,47 +171,43 @@ export component SettingsView inherits Rectangle {
} }
Rectangle { Rectangle {
vertical-stretch: 2; vertical-stretch: 1;
border-radius: 8px; border-radius: 12px;
background: Theme.surface-elevated; background: Theme.surface-elevated;
border-width: 1px;
border-color: Theme.border-main;
clip: true; clip: true;
ListView { ListView {
for r in root.remembered: Rectangle { for r in root.remembered: Rectangle {
height: 40px; height: 44px;
background: transparent; background: transparent;
HorizontalLayout { HorizontalLayout {
padding-left: 12px; padding-left: 14px;
padding-right: 8px; padding-right: 10px;
spacing: 10px; spacing: 12px;
VerticalLayout { VerticalLayout {
horizontal-stretch: 1; horizontal-stretch: 1;
alignment: center; alignment: center;
HorizontalLayout {
spacing: 6px;
Text { Text {
text: r.domain; text: r.domain;
color: Theme.text-main; color: Theme.text-main;
font-size: 13px; font-size: 14px;
font-family: "Consolas"; font-family: "Consolas";
vertical-alignment: center;
} }
Text { Text {
text: "→ " + r.browser; text: "→ " + r.browser;
color: Theme.text-hint; color: Theme.text-hint;
font-size: 12px; font-size: 12px;
vertical-alignment: center;
}
} }
} }
Rectangle { Rectangle {
width: 28px; width: 30px;
border-radius: 6px; border-radius: 6px;
background: del2-touch.has-hover ? #ef444420 : transparent; background: del2-touch.has-hover ? #ef444420 : transparent;
@@ -221,8 +221,8 @@ export component SettingsView inherits Rectangle {
Image { Image {
x: (parent.width - self.width) / 2; x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2; y: (parent.height - self.height) / 2;
width: 15px; width: 16px;
height: 15px; height: 16px;
source: @image-url("../icons/trash.svg"); source: @image-url("../icons/trash.svg");
colorize: del2-touch.has-hover ? #ef4444 : Theme.text-muted; colorize: del2-touch.has-hover ? #ef4444 : Theme.text-muted;
} }
@@ -232,4 +232,6 @@ export component SettingsView inherits Rectangle {
} }
} }
} }
}
}
} }