1use freya_core::prelude::*;
2use freya_winit::{
3 extensions::WinitPlatformExt,
4 plugins::{
5 FreyaPlugin,
6 PluginEvent,
7 PluginHandle,
8 },
9 winit::window::ResizeDirection,
10};
11use torin::{
12 position::Position,
13 prelude::{
14 Area,
15 Point2D,
16 Size2D,
17 },
18 size::Size,
19};
20
21const DIRECTIONS: [ResizeDirection; 8] = [
22 ResizeDirection::North,
23 ResizeDirection::South,
24 ResizeDirection::West,
25 ResizeDirection::East,
26 ResizeDirection::NorthWest,
27 ResizeDirection::NorthEast,
28 ResizeDirection::SouthWest,
29 ResizeDirection::SouthEast,
30];
31
32pub struct BorderlessPlugin {
54 thickness: f32,
55 corner_radius: f32,
56}
57
58impl Default for BorderlessPlugin {
59 fn default() -> Self {
60 Self {
61 thickness: 6.,
62 corner_radius: 0.,
63 }
64 }
65}
66
67impl BorderlessPlugin {
68 pub fn new() -> Self {
69 Self::default()
70 }
71
72 pub fn with_thickness(mut self, thickness: f32) -> Self {
74 self.thickness = thickness;
75 self
76 }
77
78 pub fn with_corner_radius(mut self, corner_radius: f32) -> Self {
80 self.corner_radius = corner_radius;
81 self
82 }
83}
84
85impl FreyaPlugin for BorderlessPlugin {
86 fn plugin_id(&self) -> &'static str {
87 "borderless"
88 }
89
90 fn on_event(&mut self, _event: &mut PluginEvent, _handle: PluginHandle) {}
91
92 fn root_component(&self, root: Element) -> Element {
93 BorderlessRoot {
94 thickness: self.thickness,
95 corner_radius: self.corner_radius,
96 inner: root,
97 }
98 .into_element()
99 }
100}
101
102#[derive(Clone, PartialEq)]
103struct BorderlessRoot {
104 thickness: f32,
105 corner_radius: f32,
106 inner: Element,
107}
108
109impl Component for BorderlessRoot {
110 fn render(&self) -> impl IntoElement {
111 let maximized = use_maximized();
112
113 rect()
114 .expanded()
115 .overflow(Overflow::Clip)
116 .maybe(!maximized(), |el| el.corner_radius(self.corner_radius))
117 .child(self.inner.clone())
118 .maybe(!cfg!(target_os = "macos"), |el| {
119 el.child(ResizeBands::new(self.thickness))
120 })
121 }
122}
123
124pub fn use_maximized() -> State<bool> {
126 let mut maximized = use_state(|| false);
127
128 use_side_effect(move || {
129 let _ = Platform::get().root_size.read();
130 Platform::get().with_window(Platform::window_id(), move |window| {
131 if let Some(mut maximized) = maximized.try_write() {
132 *maximized = window.fullscreen().is_some() || window.is_maximized();
133 }
134 });
135 });
136
137 maximized
138}
139
140#[derive(PartialEq)]
142pub struct ResizeBands {
143 thickness: f32,
144 key: DiffKey,
145}
146
147impl KeyExt for ResizeBands {
148 fn write_key(&mut self) -> &mut DiffKey {
149 &mut self.key
150 }
151}
152
153impl ResizeBands {
154 pub fn new(thickness: f32) -> Self {
156 Self {
157 thickness,
158 key: DiffKey::None,
159 }
160 }
161}
162
163impl Component for ResizeBands {
164 fn render(&self) -> impl IntoElement {
165 let maximized = use_maximized();
166 let size = *Platform::get().root_size.read();
167
168 rect()
169 .layer(Layer::Overlay)
170 .width(Size::px(0.))
171 .height(Size::px(0.))
172 .maybe(!maximized(), |el| {
173 el.children(
174 DIRECTIONS
175 .iter()
176 .map(|direction| self.band(*direction, size)),
177 )
178 })
179 }
180}
181
182impl ResizeBands {
183 fn band(&self, direction: ResizeDirection, size: Size2D) -> Element {
184 let band = self.thickness;
185 let corner = band * 2.;
186 let span_x = (size.width - corner * 2.).max(0.);
187 let span_y = (size.height - corner * 2.).max(0.);
188 let far_x = (size.width - band).max(0.);
189 let far_y = (size.height - band).max(0.);
190 let corner_far_x = (size.width - corner).max(0.);
191 let corner_far_y = (size.height - corner).max(0.);
192
193 let (left, top, width, height) = match direction {
194 ResizeDirection::North => (corner, 0., span_x, band),
195 ResizeDirection::South => (corner, far_y, span_x, band),
196 ResizeDirection::West => (0., corner, band, span_y),
197 ResizeDirection::East => (far_x, corner, band, span_y),
198 ResizeDirection::NorthWest => (0., 0., corner, corner),
199 ResizeDirection::NorthEast => (corner_far_x, 0., corner, corner),
200 ResizeDirection::SouthWest => (0., corner_far_y, corner, corner),
201 ResizeDirection::SouthEast => (corner_far_x, corner_far_y, corner, corner),
202 };
203 let area = Area::new(Point2D::new(left, top), Size2D::new(width, height));
204
205 rect()
206 .position(
207 Position::new_global()
208 .top(area.origin.y)
209 .left(area.origin.x),
210 )
211 .width(Size::px(area.width()))
212 .height(Size::px(area.height()))
213 .cursor(cursor(direction))
214 .on_pointer_down(move |_| {
215 Platform::get().with_window(Platform::window_id(), move |window| {
216 let _ = window.drag_resize_window(direction);
217 });
218 })
219 .into_element()
220 }
221}
222
223fn cursor(direction: ResizeDirection) -> CursorIcon {
224 match direction {
225 ResizeDirection::North | ResizeDirection::South => CursorIcon::NsResize,
226 ResizeDirection::West | ResizeDirection::East => CursorIcon::EwResize,
227 ResizeDirection::NorthWest | ResizeDirection::SouthEast => CursorIcon::NwseResize,
228 ResizeDirection::NorthEast | ResizeDirection::SouthWest => CursorIcon::NeswResize,
229 }
230}