Skip to content

Commit 9b75f08

Browse files
committed
Added multi window support
1 parent 05e22e1 commit 9b75f08

File tree

3 files changed

+64
-34
lines changed

3 files changed

+64
-34
lines changed

src/main.rs

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// In `src/main.rs` (entire file)
12
#![allow(clippy::needless_return)]
23

34
#[cfg(feature = "ui")]
@@ -17,9 +18,10 @@ use ui::{
1718
};
1819

1920
#[cfg(feature = "ui")]
20-
fn main() -> anyhow::Result<()> {
21+
fn spawn_window(registry: Rc<RefCell<Vec<AppWindow>>>) -> anyhow::Result<()> {
2122
let app = AppWindow::new()?;
2223

24+
// Initialize UI properties
2325
app.set_app_version(env!("CARGO_PKG_VERSION").into());
2426
app.set_ext_filter("".into());
2527
app.set_exclude_dirs(".git, node_modules, target, _target, .elan, .lake, .idea, .vscode, _app, .svelte-kit, .sqlx, venv, .venv, __pycache__, LICENSES, fixtures".into());
@@ -35,27 +37,34 @@ fn main() -> anyhow::Result<()> {
3537
app.set_copy_toast_text("".into());
3638
app.set_output_stats("0 chars • 0 tokens".into());
3739

40+
// Per-window state
3841
let state = Rc::new(RefCell::new(AppState {
3942
poll_interval_ms: 45_000,
4043
..Default::default()
4144
}));
4245

43-
let poll_timer = slint::Timer::default();
46+
// Periodic poll timer: capture a Weak to avoid moving `state` while borrowed
4447
{
4548
let app_weak = app.as_weak();
46-
let state = Rc::clone(&state);
4749
let interval_ms = { state.borrow().poll_interval_ms };
48-
poll_timer.start(
49-
slint::TimerMode::Repeated,
50-
std::time::Duration::from_millis(interval_ms),
51-
move || {
52-
if let Some(app) = app_weak.upgrade() {
53-
on_check_updates(&app, &state);
54-
}
55-
},
56-
);
50+
let state_weak = Rc::downgrade(&state);
51+
// borrow only long enough to call `start`, then drop before closure capture
52+
{
53+
let st = state.borrow();
54+
st.poll_timer.start(
55+
slint::TimerMode::Repeated,
56+
std::time::Duration::from_millis(interval_ms),
57+
move || {
58+
if let (Some(app), Some(state_rc)) = (app_weak.upgrade(), state_weak.upgrade())
59+
{
60+
on_check_updates(&app, &state_rc);
61+
}
62+
},
63+
);
64+
}
5765
}
5866

67+
// UI callbacks (per window)
5968
{
6069
let app_weak = app.as_weak();
6170
let state = Rc::clone(&state);
@@ -101,7 +110,6 @@ fn main() -> anyhow::Result<()> {
101110
}
102111
});
103112
}
104-
105113
{
106114
let app_weak = app.as_weak();
107115
let state = Rc::clone(&state);
@@ -111,9 +119,8 @@ fn main() -> anyhow::Result<()> {
111119
}
112120
});
113121
}
114-
115-
// Select-from-text dialog wiring kept here to avoid another extra file.
116122
{
123+
// "Select from Text…" dialog
117124
let app_weak = app.as_weak();
118125
let state = Rc::clone(&state);
119126

@@ -151,7 +158,31 @@ fn main() -> anyhow::Result<()> {
151158
});
152159
}
153160

154-
app.run()?;
161+
// Multi-window: hook "New Window" button
162+
{
163+
let registry_clone = Rc::clone(&registry);
164+
app.on_new_window(move || {
165+
let _ = spawn_window(Rc::clone(&registry_clone));
166+
});
167+
}
168+
169+
// Show this window and keep the handle alive
170+
app.show()?;
171+
registry.borrow_mut().push(app);
172+
173+
Ok(())
174+
}
175+
176+
#[cfg(feature = "ui")]
177+
fn main() -> anyhow::Result<()> {
178+
// Keep all open windows alive in this registry
179+
let registry: Rc<RefCell<Vec<AppWindow>>> = Rc::new(RefCell::new(Vec::new()));
180+
181+
// Create the initial window
182+
spawn_window(Rc::clone(&registry))?;
183+
184+
// One global event loop; closes when all windows are closed
185+
slint::run_event_loop()?;
155186
Ok(())
156187
}
157188

src/ui/state.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub struct AppState {
2929
pub fs_event_rx: Option<std::sync::mpsc::Receiver<notify::Result<notify::Event>>>,
3030
pub fs_pump_timer: slint::Timer,
3131
pub full_output_text: String,
32+
pub poll_timer: slint::Timer, // NEW: keep per-window periodic update timer alive
3233
}
3334

3435
pub type SharedState = Rc<RefCell<AppState>>;

ui/app.slint

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export component SelectFromTextDialog inherits Window {
5757
}
5858
}
5959

60+
// In `ui/app.slint`, update the `AppWindow` component (complete component)
6061
export component AppWindow inherits Window {
6162
width: 1300px;
6263
height: 650px;
@@ -80,7 +81,6 @@ export component AppWindow inherits Window {
8081
in-out property <[string]> output-lines;
8182
in-out property <string> output-stats;
8283

83-
8484
in-out property <bool> show-copy-toast;
8585
in-out property <string> copy-toast-text;
8686

@@ -91,37 +91,40 @@ export component AppWindow inherits Window {
9191
callback generate-output();
9292
callback copy-output();
9393
callback select-from-text();
94-
94+
callback new-window(); // NEW
9595

9696
VerticalBox {
9797
spacing: 10px;
9898

99-
// Header row
10099
HorizontalBox {
101100
spacing: 8px;
102101

103102
Rectangle {
104103
horizontal-stretch: 1;
105-
width: 360px;
104+
width: 180px;
106105
Button {
107106
horizontal-stretch: 1;
108107
text: "Select Folder";
109108

110-
width: parent.width / 2;
109+
width: 110px;
111110
height: 30px;
112111
clicked => { root.select-folder(); }
113112
}
114113
}
115-
Rectangle { horizontal-stretch: 1; background: transparent; } // spacer
116114

117-
// Keep the checkboxes a consistent height so focus/hover doesn't change height
115+
Button {
116+
text: "New Window";
117+
clicked => { root.new-window(); }
118+
}
119+
120+
Rectangle { horizontal-stretch: 1; background: transparent; }
121+
118122
CheckBox { text: "Hierarchy Only"; checked <=> root.hierarchy-only; height: 30px; width: 150px; horizontal-stretch: 1;}
119123
CheckBox { text: "Directories Only"; checked <=> root.dirs-only; height: 30px; width: 160px; horizontal-stretch: 1; }
120124

121-
// <-- Reserve space for the timestamp so it won't push things around
122125
Rectangle {
123126
horizontal-stretch: 1;
124-
width: 290px; // pick a width that fits your longest string
127+
width: 290px;
125128
height: 30px;
126129
background: transparent;
127130
Text {
@@ -130,7 +133,7 @@ export component AppWindow inherits Window {
130133
text: root.last-refresh;
131134
horizontal-alignment: right;
132135
vertical-alignment: center;
133-
overflow: elide; // if it gets too long, don't expand the row
136+
overflow: elide;
134137
}
135138
}
136139

@@ -144,6 +147,8 @@ export component AppWindow inherits Window {
144147
clicked => { root.copy-output(); }
145148
}
146149

150+
151+
147152
Text {
148153
visible: root.show-copy-toast;
149154
text: root.copy-toast-text;
@@ -152,12 +157,9 @@ export component AppWindow inherits Window {
152157
}
153158
}
154159

155-
156-
// Main 3-column content
157160
HorizontalBox {
158161
spacing: 12px;
159162

160-
// LEFT PANE — inputs
161163
VerticalBox {
162164
width: 360px;
163165

@@ -205,7 +207,7 @@ export component AppWindow inherits Window {
205207
border-width: 1px;
206208
border-radius: 4px;
207209
border-color: Palette.border;
208-
background: Palette.alternate-background.darker(0.06); // slightly darker than UI
210+
background: Palette.alternate-background.darker(0.06);
209211
ScrollView {
210212
viewport1 := VerticalBox {
211213
spacing: 0px;
@@ -249,8 +251,6 @@ export component AppWindow inherits Window {
249251
}
250252
}
251253

252-
253-
// RIGHT PANE — output
254254
VerticalBox {
255255
padding-top: 24px;
256256
spacing: 6px;
@@ -272,12 +272,10 @@ export component AppWindow inherits Window {
272272
read-only: true;
273273
wrap: no-wrap;
274274

275-
// Make it actually fill the bordered rectangle:
276275
x: 0; y: 0;
277276
width: parent.width;
278277
height: parent.height;
279278
has-focus: false;
280-
// Readability
281279
font-size: 11px;
282280

283281
}

0 commit comments

Comments
 (0)