Skip to content

Commit 06904b9

Browse files
authored
feat: Implement window-level data handling (#26)
Previously, when you have one window, you can edit and save file normally. When you open a new window and draw some new thing, then hit save, it saves the new content into the file from the original window! You may think it's because in life, you can never have more than one nice thing, that you should never ever be multitasking, at any point, you should be focused, comitted to only one thing at a time is a key for a success life, as many successful people said. Turned out, it's a bug! My bad, I didn't know the fact that ApplicationState is tracked at application's root level, not on window level. So all windows would share the same ApplicationState. To fix this issue, I need to restructure the ApplicationState, split it up to a HashMap is the first thing that come to my mind, and it looks good enough. So here we go.
1 parent 513c561 commit 06904b9

File tree

5 files changed

+112
-38
lines changed

5 files changed

+112
-38
lines changed

src/data/mod.rs

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use druid::Data;
1+
use druid::{Data, WindowId};
2+
use std::collections::HashMap;
23

34
use crate::tools::DrawingTools;
45

@@ -10,8 +11,23 @@ pub mod shape_list;
1011

1112
mod overlap;
1213

13-
#[derive(Clone, PartialEq, Data)]
14-
pub struct ApplicationState {
14+
#[derive(Clone, PartialEq, Data, Debug)]
15+
pub struct WindowData {
1516
pub mode: DrawingTools,
1617
pub current_file: Option<String>,
1718
}
19+
20+
impl WindowData {
21+
pub fn new() -> Self {
22+
Self {
23+
mode: DrawingTools::Select,
24+
current_file: None,
25+
}
26+
}
27+
}
28+
29+
#[derive(Clone, PartialEq, Data, Debug)]
30+
pub struct ApplicationState {
31+
#[data(eq)]
32+
pub windows: HashMap<WindowId, WindowData>,
33+
}

src/main.rs

+14-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use druid::{
55
AppDelegate, AppLauncher, Application, Command, DelegateCtx, Env, Handled, LifeCycle,
66
PlatformError, Point, Target, Widget, WidgetPod, WindowDesc, WindowId,
77
};
8+
use std::collections::HashMap;
89

910
#[macro_use]
1011
mod macros;
@@ -14,6 +15,7 @@ mod shapes;
1415
mod tools;
1516
mod widgets;
1617

18+
use crate::data::WindowData;
1719
use data::ApplicationState;
1820
use widgets::{grid::CanvasGrid, layout::StackLayout, toolbar::ToolBarWidget};
1921

@@ -95,12 +97,13 @@ impl AppDelegate<ApplicationState> for Delegate {
9597
fn window_removed(
9698
&mut self,
9799
id: WindowId,
98-
_data: &mut ApplicationState,
100+
data: &mut ApplicationState,
99101
_env: &Env,
100102
_ctx: &mut DelegateCtx,
101103
) {
102104
if let Some(pos) = self.windows.iter().position(|x| *x == id) {
103105
self.windows.remove(pos);
106+
data.windows.remove(&id);
104107
}
105108
if self.windows.len() == 0 {
106109
// Quit when the window is closed
@@ -112,11 +115,12 @@ impl AppDelegate<ApplicationState> for Delegate {
112115
&mut self,
113116
id: WindowId,
114117
_handle: druid::WindowHandle,
115-
_data: &mut ApplicationState,
118+
data: &mut ApplicationState,
116119
_env: &Env,
117120
_ctx: &mut DelegateCtx,
118121
) {
119122
self.windows.push(id);
123+
data.windows.insert(id.to_owned(), WindowData::new());
120124
}
121125

122126
fn command(
@@ -139,17 +143,18 @@ impl AppDelegate<ApplicationState> for Delegate {
139143
fn main() -> Result<(), PlatformError> {
140144
// https://github.com/linebender/druid/pull/1701/files
141145
// Follow the above PR for transparent title bar status
142-
let app = AppLauncher::with_window(
143-
WindowDesc::new(MainWindow::new())
144-
.title("ASCII-d")
145-
.window_size((640.0, 480.0)),
146-
);
146+
let window = WindowDesc::new(MainWindow::new())
147+
.title("ASCII-d")
148+
.window_size((640.0, 480.0));
149+
let win_id = window.id.to_owned();
150+
let app = AppLauncher::with_window(window);
151+
let mut window_map = HashMap::new();
152+
window_map.insert(win_id, WindowData::new());
147153
app.delegate(Delegate {
148154
windows: Vec::new(),
149155
})
150156
.launch(ApplicationState {
151-
mode: tools::DrawingTools::Select,
152-
current_file: None,
157+
windows: window_map,
153158
})?;
154159
Ok(())
155160
}

src/tools/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub mod rect;
1818
pub mod select;
1919
pub mod text;
2020

21-
#[derive(Clone, Copy, PartialEq, Data)]
21+
#[derive(Clone, Copy, PartialEq, Data, Debug)]
2222
pub enum DrawingTools {
2323
Select = 0,
2424
Line = 1,

src/widgets/grid.rs

+29-13
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ impl Widget<ApplicationState> for CanvasGrid {
7878
data: &mut ApplicationState,
7979
_env: &druid::Env,
8080
) {
81+
let win_data = data
82+
.windows
83+
.get_mut(&ctx.window_id())
84+
.expect("Invalid WindowID");
8185
match event {
8286
Event::WindowConnected => {
8387
// Have to request focus in order to get keyboard event
@@ -86,23 +90,23 @@ impl Widget<ApplicationState> for CanvasGrid {
8690
Event::KeyDown(event) => {
8791
match event.code {
8892
Code::Escape => {
89-
data.mode = DrawingTools::Select;
93+
win_data.mode = DrawingTools::Select;
9094
}
9195
keycode => {
92-
if data.mode != DrawingTools::Text {
96+
if win_data.mode != DrawingTools::Text {
9397
// Only handle shortcut key if not in text mode
9498
match keycode {
9599
Code::Digit1 | Code::KeyL | Code::KeyA => {
96-
data.mode = DrawingTools::Line;
100+
win_data.mode = DrawingTools::Line;
97101
}
98102
Code::Digit2 | Code::KeyR => {
99-
data.mode = DrawingTools::Rect;
103+
win_data.mode = DrawingTools::Rect;
100104
}
101105
Code::Digit3 | Code::KeyT => {
102-
data.mode = DrawingTools::Text;
106+
win_data.mode = DrawingTools::Text;
103107
}
104108
Code::Digit4 | Code::KeyE => {
105-
data.mode = DrawingTools::Eraser;
109+
win_data.mode = DrawingTools::Eraser;
106110
}
107111
Code::Delete | Code::Backspace => {
108112
self.grid_list.erase_highlighted();
@@ -214,7 +218,7 @@ impl Widget<ApplicationState> for CanvasGrid {
214218
if let Some(file_name) =
215219
file_info.path().to_str().and_then(|s| Some(s.to_string()))
216220
{
217-
data.current_file = Some(file_name.clone());
221+
win_data.current_file = Some(file_name.clone());
218222
ctx.window().set_title(file_name.as_str());
219223
}
220224
}
@@ -227,7 +231,7 @@ impl Widget<ApplicationState> for CanvasGrid {
227231
if let Some(file_name) =
228232
file_info.path().to_str().and_then(|s| Some(s.to_string()))
229233
{
230-
data.current_file = Some(file_name.clone());
234+
win_data.current_file = Some(file_name.clone());
231235
ctx.window().set_title(file_name.as_str());
232236
}
233237
}
@@ -261,14 +265,22 @@ impl Widget<ApplicationState> for CanvasGrid {
261265
data: &ApplicationState,
262266
_env: &druid::Env,
263267
) {
264-
if old_data.mode != data.mode {
265-
self.tool_manager.set_tool(data.mode);
266-
if old_data.mode == DrawingTools::Text {
268+
let win_data = data
269+
.windows
270+
.get(&ctx.window_id())
271+
.expect("Invalid WindowID");
272+
let old_win_data = old_data
273+
.windows
274+
.get(&ctx.window_id())
275+
.expect("Invalid WindowID");
276+
if old_win_data.mode != win_data.mode {
277+
self.tool_manager.set_tool(win_data.mode);
278+
if old_win_data.mode == DrawingTools::Text {
267279
self.shape_list.commit_all(&mut self.grid_list);
268280
self.grid_list.clear_all_highlight();
269281
}
270282

271-
match data.mode {
283+
match win_data.mode {
272284
DrawingTools::Select => ctx.set_cursor(&Cursor::Arrow),
273285
DrawingTools::Line => ctx.set_cursor(&Cursor::Crosshair),
274286
DrawingTools::Rect => ctx.set_cursor(&Cursor::Crosshair),
@@ -300,6 +312,10 @@ impl Widget<ApplicationState> for CanvasGrid {
300312
}
301313

302314
fn paint(&mut self, ctx: &mut druid::PaintCtx, data: &ApplicationState, env: &druid::Env) {
315+
let win_data = data
316+
.windows
317+
.get(&ctx.window_id())
318+
.expect("Invalid WindowID");
303319
let current_theme = unsafe { CURRENT_THEME.current() };
304320
let bound = ctx.region().bounding_box();
305321
let brush = ctx.solid_brush(current_theme.bg);
@@ -370,7 +386,7 @@ impl Widget<ApplicationState> for CanvasGrid {
370386
h_row * cell_height + cell_height,
371387
);
372388

373-
if data.mode != DrawingTools::Text {
389+
if win_data.mode != DrawingTools::Text {
374390
ctx.fill(h_rect, &highlight_brush);
375391
} else {
376392
ctx.stroke(h_rect, &preview_brush, 1.0);

src/widgets/toolbar.rs

+49-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::path::PathBuf;
22

33
use super::image_button::ImageButton;
4+
use crate::data::WindowData;
45
use crate::{consts::BUTTON_HIGHLIGHT_COMMAND, data::ApplicationState, tools::DrawingTools};
56
use druid::{
67
widget::{CrossAxisAlignment, Flex, MainAxisAlignment},
@@ -34,8 +35,12 @@ impl ToolBarWidget {
3435
DrawingTools::Select.to_string(),
3536
)
3637
.on_click(|ctx, data: &mut ApplicationState, _env| {
38+
let win_data = data
39+
.windows
40+
.get_mut(&ctx.window_id())
41+
.expect("Invalid WindowID");
3742
let tool = DrawingTools::Select;
38-
data.mode = tool;
43+
win_data.mode = tool;
3944
ctx.submit_notification(BUTTON_HIGHLIGHT_COMMAND.with(tool.to_string()));
4045
ctx.set_handled();
4146
}),
@@ -48,8 +53,12 @@ impl ToolBarWidget {
4853
DrawingTools::Line.to_string(),
4954
)
5055
.on_click(|ctx, data: &mut ApplicationState, _env| {
56+
let win_data = data
57+
.windows
58+
.get_mut(&ctx.window_id())
59+
.expect("Invalid WindowID");
5160
let tool = DrawingTools::Line;
52-
data.mode = tool;
61+
win_data.mode = tool;
5362
ctx.submit_notification(BUTTON_HIGHLIGHT_COMMAND.with(tool.to_string()));
5463
ctx.set_handled();
5564
}),
@@ -62,8 +71,12 @@ impl ToolBarWidget {
6271
DrawingTools::Rect.to_string(),
6372
)
6473
.on_click(|ctx, data: &mut ApplicationState, _env| {
74+
let win_data = data
75+
.windows
76+
.get_mut(&ctx.window_id())
77+
.expect("Invalid WindowID");
6578
let tool = DrawingTools::Rect;
66-
data.mode = tool;
79+
win_data.mode = tool;
6780
ctx.submit_notification(BUTTON_HIGHLIGHT_COMMAND.with(tool.to_string()));
6881
ctx.set_handled();
6982
}),
@@ -76,8 +89,12 @@ impl ToolBarWidget {
7689
DrawingTools::Text.to_string(),
7790
)
7891
.on_click(|ctx, data: &mut ApplicationState, _env| {
92+
let win_data = data
93+
.windows
94+
.get_mut(&ctx.window_id())
95+
.expect("Invalid WindowID");
7996
let tool = DrawingTools::Text;
80-
data.mode = tool;
97+
win_data.mode = tool;
8198
ctx.submit_notification(BUTTON_HIGHLIGHT_COMMAND.with(tool.to_string()));
8299
ctx.set_handled();
83100
}),
@@ -90,8 +107,12 @@ impl ToolBarWidget {
90107
DrawingTools::Eraser.to_string(),
91108
)
92109
.on_click(|ctx, data: &mut ApplicationState, _env| {
110+
let win_data = data
111+
.windows
112+
.get_mut(&ctx.window_id())
113+
.expect("Invalid WindowID");
93114
let tool = DrawingTools::Eraser;
94-
data.mode = tool;
115+
win_data.mode = tool;
95116
ctx.submit_notification(BUTTON_HIGHLIGHT_COMMAND.with(tool.to_string()));
96117
ctx.set_handled();
97118
}),
@@ -114,7 +135,11 @@ impl ToolBarWidget {
114135
.with_child(
115136
ImageButton::new(save_icon, Size::new(26.0, 26.0), String::new()).on_click(
116137
move |ctx, data: &mut ApplicationState, _env| {
117-
save_to_file(data, ctx);
138+
let win_data = data
139+
.windows
140+
.get_mut(&ctx.window_id())
141+
.expect("Invalid WindowID");
142+
save_to_file(win_data, ctx);
118143
ctx.set_handled();
119144
},
120145
),
@@ -142,7 +167,7 @@ fn open_from_file(ctx: &mut druid::EventCtx) {
142167
ctx.submit_command(druid::commands::SHOW_OPEN_PANEL.with(open_dialog_options));
143168
}
144169

145-
fn save_to_file(data: &mut ApplicationState, ctx: &mut druid::EventCtx) {
170+
fn save_to_file(data: &mut WindowData, ctx: &mut druid::EventCtx) {
146171
let save_dialog_options = FileDialogOptions::new()
147172
.allowed_types(vec![FileSpec::TEXT])
148173
.default_type(FileSpec::TEXT)
@@ -172,21 +197,25 @@ impl Widget<ApplicationState> for ToolBarWidget {
172197
self.left_buttons.event(ctx, event, data, env);
173198
self.right_buttons.event(ctx, event, data, env);
174199

200+
let win_data = data
201+
.windows
202+
.get_mut(&ctx.window_id())
203+
.expect("Invalid WindowID");
175204
// Prevent the mouse event to be propagated to underlying widgets
176205
match event {
177206
Event::WindowConnected => {
178-
ctx.submit_command(BUTTON_HIGHLIGHT_COMMAND.with(data.mode.to_string()));
207+
ctx.submit_command(BUTTON_HIGHLIGHT_COMMAND.with(win_data.mode.to_string()));
179208
}
180209
Event::Notification(notification) => {
181210
if let Some(name) = notification.get(BUTTON_HIGHLIGHT_COMMAND) {
182211
ctx.submit_command(BUTTON_HIGHLIGHT_COMMAND.with(name.to_string()));
183212
}
184213
}
185214
Event::KeyDown(event) => {
186-
if data.mode != DrawingTools::Text && event.mods.meta() || event.mods.ctrl() {
215+
if win_data.mode != DrawingTools::Text && event.mods.meta() || event.mods.ctrl() {
187216
match event.code {
188217
druid::Code::KeyS => {
189-
save_to_file(data, ctx);
218+
save_to_file(win_data, ctx);
190219
}
191220
druid::Code::KeyO => {
192221
open_from_file(ctx);
@@ -218,8 +247,16 @@ impl Widget<ApplicationState> for ToolBarWidget {
218247
data: &ApplicationState,
219248
env: &druid::Env,
220249
) {
221-
if old_data.mode != data.mode {
222-
ctx.submit_command(BUTTON_HIGHLIGHT_COMMAND.with(data.mode.to_string()));
250+
let win_data = data
251+
.windows
252+
.get(&ctx.window_id())
253+
.expect("Invalid WindowID");
254+
let old_win_data = old_data
255+
.windows
256+
.get(&ctx.window_id())
257+
.expect("Invalid WindowID");
258+
if old_win_data.mode != win_data.mode {
259+
ctx.submit_command(BUTTON_HIGHLIGHT_COMMAND.with(win_data.mode.to_string()));
223260
}
224261
self.left_buttons.update(ctx, data, env);
225262
self.right_buttons.update(ctx, data, env);

0 commit comments

Comments
 (0)