1#![windows_subsystem = "windows"]
19
20use druid::widget::prelude::*;
21use druid::widget::{
22 Align, BackgroundBrush, Button, Controller, ControllerHost, Flex, Label, Padding,
23};
24use druid::Target::Global;
25use druid::{
26 commands as sys_cmds, AppDelegate, AppLauncher, Application, Color, Command, Data, DelegateCtx,
27 Handled, LocalizedString, Menu, MenuItem, Target, WindowDesc, WindowHandle, WindowId,
28};
29use tracing::info;
30
31#[derive(Debug, Clone, Default, Data)]
32struct State {
33 menu_count: usize,
34 selected: usize,
35 glow_hot: bool,
36}
37
38pub fn main() {
39 let main_window = WindowDesc::new(ui_builder()).menu(make_menu).title(
40 LocalizedString::new("multiwin-demo-window-title").with_placeholder("Many windows!"),
41 );
42 AppLauncher::with_window(main_window)
43 .delegate(Delegate {
44 windows: Vec::new(),
45 })
46 .log_to_console()
47 .launch(State::default())
48 .expect("launch failed");
49}
50
51fn ui_builder() -> impl Widget<State> {
52 let text = LocalizedString::new("hello-counter")
53 .with_arg("count", |data: &State, _env| data.menu_count.into());
54 let label = Label::new(text);
55 let inc_button =
56 Button::<State>::new("Add menu item").on_click(|_ctx, data, _env| data.menu_count += 1);
57 let dec_button = Button::<State>::new("Remove menu item")
58 .on_click(|_ctx, data, _env| data.menu_count = data.menu_count.saturating_sub(1));
59 let new_button = Button::<State>::new("New window").on_click(|ctx, _data, _env| {
60 ctx.submit_command(sys_cmds::NEW_FILE.to(Global));
61 });
62 let quit_button = Button::<State>::new("Quit app").on_click(|_ctx, _data, _env| {
63 Application::global().quit();
64 });
65
66 let mut col = Flex::column();
67 col.add_flex_child(Align::centered(Padding::new(5.0, label)), 1.0);
68 let mut row = Flex::row();
69 row.add_child(Padding::new(5.0, inc_button));
70 row.add_child(Padding::new(5.0, dec_button));
71 col.add_flex_child(Align::centered(row), 1.0);
72 let mut row = Flex::row();
73 row.add_child(Padding::new(5.0, new_button));
74 row.add_child(Padding::new(5.0, quit_button));
75 col.add_flex_child(Align::centered(row), 1.0);
76 let content = ControllerHost::new(col, ContextMenuController);
77 Glow::new(content)
78}
79
80struct Glow<W> {
81 inner: W,
82}
83
84impl<W> Glow<W> {
85 pub fn new(inner: W) -> Glow<W> {
86 Glow { inner }
87 }
88}
89
90impl<W: Widget<State>> Widget<State> for Glow<W> {
91 fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut State, env: &Env) {
92 self.inner.event(ctx, event, data, env);
93 }
94
95 fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &State, env: &Env) {
96 if let LifeCycle::HotChanged(_) = event {
97 ctx.request_paint();
98 }
99 self.inner.lifecycle(ctx, event, data, env);
100 }
101
102 fn update(&mut self, ctx: &mut UpdateCtx, old_data: &State, data: &State, env: &Env) {
103 if old_data.glow_hot != data.glow_hot {
104 ctx.request_paint();
105 }
106 self.inner.update(ctx, old_data, data, env);
107 }
108
109 fn layout(
110 &mut self,
111 ctx: &mut LayoutCtx,
112 bc: &BoxConstraints,
113 data: &State,
114 env: &Env,
115 ) -> Size {
116 self.inner.layout(ctx, bc, data, env)
117 }
118
119 fn paint(&mut self, ctx: &mut PaintCtx, data: &State, env: &Env) {
120 if data.glow_hot && ctx.is_hot() {
121 BackgroundBrush::Color(Color::rgb8(200, 55, 55)).paint(ctx, data, env);
122 }
123 self.inner.paint(ctx, data, env);
124 }
125}
126
127struct ContextMenuController;
128struct Delegate {
129 windows: Vec<WindowId>,
130}
131
132impl<W: Widget<State>> Controller<State, W> for ContextMenuController {
133 fn event(
134 &mut self,
135 child: &mut W,
136 ctx: &mut EventCtx,
137 event: &Event,
138 data: &mut State,
139 env: &Env,
140 ) {
141 match event {
142 Event::MouseDown(ref mouse) if mouse.button.is_right() => {
143 ctx.show_context_menu(make_context_menu(), mouse.pos);
144 }
145 _ => child.event(ctx, event, data, env),
146 }
147 }
148}
149
150impl AppDelegate<State> for Delegate {
151 fn command(
152 &mut self,
153 ctx: &mut DelegateCtx,
154 _target: Target,
155 cmd: &Command,
156 data: &mut State,
157 _env: &Env,
158 ) -> Handled {
159 if cmd.is(sys_cmds::NEW_FILE) {
160 let new_win = WindowDesc::new(ui_builder())
161 .menu(make_menu)
162 .window_size((data.selected as f64 * 100.0 + 300.0, 500.0));
163 ctx.new_window(new_win);
164 Handled::Yes
165 } else {
166 Handled::No
167 }
168 }
169
170 fn window_added(
171 &mut self,
172 id: WindowId,
173 _handle: WindowHandle,
174 _data: &mut State,
175 _env: &Env,
176 _ctx: &mut DelegateCtx,
177 ) {
178 info!("Window added, id: {:?}", id);
179 self.windows.push(id);
180 }
181
182 fn window_removed(
183 &mut self,
184 id: WindowId,
185 _data: &mut State,
186 _env: &Env,
187 _ctx: &mut DelegateCtx,
188 ) {
189 info!("Window removed, id: {:?}", id);
190 if let Some(pos) = self.windows.iter().position(|x| *x == id) {
191 self.windows.remove(pos);
192 }
193 }
194}
195
196#[allow(unused_assignments)]
197fn make_menu(_: Option<WindowId>, state: &State, _: &Env) -> Menu<State> {
198 let mut base = Menu::empty();
199 #[cfg(target_os = "macos")]
200 {
201 base = druid::platform_menus::mac::menu_bar();
202 }
203 #[cfg(any(
204 target_os = "windows",
205 target_os = "freebsd",
206 target_os = "linux",
207 target_os = "openbsd"
208 ))]
209 {
210 base = base.entry(druid::platform_menus::win::file::default());
211 }
212 if state.menu_count != 0 {
213 let mut custom = Menu::new(LocalizedString::new("Custom"));
214
215 for i in 1..=state.menu_count {
216 custom = custom.entry(
217 MenuItem::new(
218 LocalizedString::new("hello-counter")
219 .with_arg("count", move |_: &State, _| i.into()),
220 )
221 .on_activate(move |_ctx, data, _env| data.selected = i)
222 .enabled_if(move |_data, _env| i % 3 != 0)
223 .selected_if(move |data, _env| i == data.selected),
224 );
225 }
226 base = base.entry(custom);
227 }
228 base.rebuild_on(|old_data, data, _env| old_data.menu_count != data.menu_count)
229}
230
231fn make_context_menu() -> Menu<State> {
232 Menu::empty()
233 .entry(
234 MenuItem::new(LocalizedString::new("Increment"))
235 .on_activate(|_ctx, data: &mut State, _env| data.menu_count += 1),
236 )
237 .entry(
238 MenuItem::new(LocalizedString::new("Decrement")).on_activate(
239 |_ctx, data: &mut State, _env| data.menu_count = data.menu_count.saturating_sub(1),
240 ),
241 )
242 .entry(
243 MenuItem::new(LocalizedString::new("Glow when hot"))
244 .on_activate(|_ctx, data: &mut State, _env| data.glow_hot = !data.glow_hot),
245 )
246}