1use druid::widget::prelude::*;
23use druid::widget::{Button, Container, Flex, Label, LineBreaking, Widget};
24use druid::{AppLauncher, Color, Lens, Point, Rect, Region, WidgetExt, WidgetPod, WindowDesc};
25
26const EXAMPLE_BORDER_SIZE: f64 = 3.0;
27const INFO_TEXT: &str = "Only this text and the borders can be interacted with.
28You can click through the other parts
29
30This demo is useful for observing the limitations of each OS.
31- Windows is well supported. Observation: When the titlebar is enabled and the input region is set, the border becomes invisible. Always on top is supported.
32- macOS has good support, but doesn't allow toggling titlebar after the window is opened. Also, it just makes transparent regions transparent automatically when set to have no titlebar. Always on top is supported.
33- Linux support varies by desktop environment and display server. Wayland is much more restrictive, with it not allowing things like setting position and always on top. Fortunately desktop environments often allow you to manually set window decoration and always on top on the Window itself. The offsets can differ between desktop environments, and sometimes you need to open the window with the titlebar, then turn it off, for it to work.";
34
35#[derive(Clone, Data, Lens)]
36struct AppState {
37 limit_input_region: bool,
38 show_titlebar: bool,
39 always_on_top: bool,
40}
41
42struct InputRegionExampleWidget {
43 info_label: WidgetPod<AppState, Container<AppState>>,
44 controls: WidgetPod<AppState, Flex<AppState>>,
45}
46
47impl InputRegionExampleWidget {
48 pub fn new() -> Self {
49 let info_label = Label::new(INFO_TEXT)
50 .with_line_break_mode(LineBreaking::WordWrap)
51 .padding(20.0)
52 .background(Color::rgba(0.2, 0.2, 0.2, 1.0));
53 let toggle_input_region = Button::new("Toggle Input Region")
54 .on_click(|ctx, data: &mut bool, _: &Env| {
55 *data = !*data;
56 tracing::debug!("Setting input region toggle to: {}", *data);
57 ctx.request_layout();
58 })
59 .lens(AppState::limit_input_region);
60 let toggle_titlebar = Button::new("Toggle TitleBar")
61 .on_click(|ctx, data: &mut bool, _: &Env| {
62 *data = !*data;
63 tracing::debug!("Setting titlebar visibility to: {}", *data);
64 ctx.window().show_titlebar(*data);
65 ctx.request_layout();
66 })
67 .lens(AppState::show_titlebar);
68 let toggle_always_on_top = Button::new("Toggle Always On Top")
69 .on_click(|ctx, data: &mut bool, _: &Env| {
70 *data = !*data;
71 tracing::debug!("Setting always on top to: {}", *data);
72 ctx.window().set_always_on_top(*data);
73 })
74 .lens(AppState::always_on_top);
75 let controls_flex = Flex::row()
76 .with_child(toggle_input_region)
77 .with_child(toggle_titlebar)
78 .with_child(toggle_always_on_top);
79 Self {
80 info_label: WidgetPod::new(info_label),
81 controls: WidgetPod::new(controls_flex),
82 }
83 }
84}
85
86impl Widget<AppState> for InputRegionExampleWidget {
87 fn event(
88 &mut self,
89 ctx: &mut druid::EventCtx,
90 event: &druid::Event,
91 data: &mut AppState,
92 env: &druid::Env,
93 ) {
94 self.info_label.event(ctx, event, data, env);
95 self.controls.event(ctx, event, data, env);
96 }
97
98 fn lifecycle(
99 &mut self,
100 ctx: &mut druid::LifeCycleCtx,
101 event: &druid::LifeCycle,
102 data: &AppState,
103 env: &druid::Env,
104 ) {
105 self.info_label.lifecycle(ctx, event, data, env);
106 self.controls.lifecycle(ctx, event, data, env);
107 }
108
109 fn update(
110 &mut self,
111 ctx: &mut druid::UpdateCtx,
112 _old_data: &AppState,
113 data: &AppState,
114 env: &druid::Env,
115 ) {
116 self.info_label.update(ctx, data, env);
117 self.controls.update(ctx, data, env);
118 }
119
120 fn layout(
121 &mut self,
122 ctx: &mut druid::LayoutCtx,
123 bc: &druid::BoxConstraints,
124 data: &AppState,
125 env: &druid::Env,
126 ) -> druid::Size {
127 let mut interactable_area = Region::EMPTY;
128 let smaller_bc = BoxConstraints::new(
129 Size::new(0.0, 0.0),
130 Size::new(bc.max().width - 100.0, bc.max().height - 100.0),
131 );
132 let full_bc = BoxConstraints::new(Size::new(0.0, 0.0), bc.max());
133 let _label_size = self.info_label.layout(ctx, &smaller_bc, data, env);
134 let controls_size = self.controls.layout(ctx, &full_bc, data, env);
135
136 let text_origin_point = Point::new(50.0, 50.0 + controls_size.height);
137 self.info_label.set_origin(ctx, text_origin_point);
138 let controls_origin_point = Point::new(EXAMPLE_BORDER_SIZE, EXAMPLE_BORDER_SIZE);
139 self.controls.set_origin(ctx, controls_origin_point);
140
141 let left_rect = Rect::new(0.0, 0.0, EXAMPLE_BORDER_SIZE, bc.max().height);
143 let right_rect = Rect::new(
144 bc.max().width - EXAMPLE_BORDER_SIZE,
145 0.0,
146 bc.max().width,
147 bc.max().height,
148 );
149 let bottom_rect = Rect::new(
150 0.0,
151 bc.max().height - EXAMPLE_BORDER_SIZE,
152 bc.max().width,
153 bc.max().height,
154 );
155 interactable_area.add_rect(left_rect);
156 interactable_area.add_rect(right_rect);
157 interactable_area.add_rect(bottom_rect);
158 interactable_area.add_rect(self.info_label.layout_rect());
159 interactable_area.add_rect(self.controls.layout_rect());
160
161 if data.limit_input_region {
162 ctx.window().set_input_region(Some(interactable_area));
163 } else {
164 ctx.window().set_input_region(None);
165 }
166
167 bc.max()
168 }
169
170 fn paint(&mut self, ctx: &mut druid::PaintCtx, data: &AppState, env: &druid::Env) {
171 let window_area = ctx.size();
172 let left_rect = Rect::new(0.0, 0.0, EXAMPLE_BORDER_SIZE, window_area.height);
173 let right_rect = Rect::new(
174 window_area.width - EXAMPLE_BORDER_SIZE,
175 0.0,
176 window_area.width,
177 window_area.height,
178 );
179 let bottom_rect = Rect::new(
180 0.0,
181 window_area.height - EXAMPLE_BORDER_SIZE,
182 window_area.width,
183 window_area.height,
184 );
185
186 ctx.fill(left_rect, &Color::rgba(1.0, 0., 0., 0.7));
187 ctx.fill(right_rect, &Color::rgba(1.0, 0., 0., 0.7));
188 ctx.fill(bottom_rect, &Color::rgba(1.0, 0., 0., 0.7));
189 self.info_label.paint(ctx, data, env);
190 self.controls.paint(ctx, data, env);
191 }
192}
193
194fn main() {
195 let main_window = WindowDesc::new(InputRegionExampleWidget::new())
196 .title("Input Region Demo")
197 .window_size((750.0, 500.0))
198 .with_min_size((650.0, 450.0))
199 .show_titlebar(false)
204 .transparent(true);
205
206 let state = AppState {
207 limit_input_region: true,
208 always_on_top: false,
209 show_titlebar: false,
210 };
211
212 AppLauncher::with_window(main_window)
213 .log_to_console()
214 .launch(state)
215 .expect("Failed to launch application");
216}