use freya_core::prelude::*;
use freya_engine::prelude::{
ClipOp,
Color,
RRect,
SkRect,
};
use freya_winit::{
extensions::WinitPlatformExt,
plugins::{
FreyaPlugin,
PluginEvent,
PluginHandle,
},
winit::window::ResizeDirection,
};
use torin::{
position::Position,
prelude::{
Area,
Point2D,
Size2D,
},
size::Size,
};
const DIRECTIONS: [ResizeDirection; 8] = [
ResizeDirection::North,
ResizeDirection::South,
ResizeDirection::West,
ResizeDirection::East,
ResizeDirection::NorthWest,
ResizeDirection::NorthEast,
ResizeDirection::SouthWest,
ResizeDirection::SouthEast,
];
pub struct BorderlessPlugin {
thickness: f32,
corner_radius: f32,
}
impl Default for BorderlessPlugin {
fn default() -> Self {
Self {
thickness: 6.,
corner_radius: 0.,
}
}
}
impl BorderlessPlugin {
pub fn new() -> Self {
Self::default()
}
pub fn with_thickness(mut self, thickness: f32) -> Self {
self.thickness = thickness;
self
}
pub fn with_corner_radius(mut self, corner_radius: f32) -> Self {
self.corner_radius = corner_radius;
self
}
}
impl FreyaPlugin for BorderlessPlugin {
fn plugin_id(&self) -> &'static str {
"borderless"
}
fn on_event(&mut self, event: &mut PluginEvent, _handle: PluginHandle) {
if self.corner_radius <= 0. {
return;
}
match event {
PluginEvent::BeforeRender { canvas, window, .. } => {
canvas.clear(Color::TRANSPARENT);
canvas.save();
if window.fullscreen().is_some() || window.is_maximized() {
return;
}
let size = window.inner_size();
let radius = self.corner_radius * window.scale_factor() as f32;
let rounded_window = RRect::new_rect_xy(
SkRect::from_wh(size.width as f32, size.height as f32),
radius,
radius,
);
canvas.clip_rrect(rounded_window, ClipOp::Intersect, true);
}
PluginEvent::AfterRender { canvas, .. } => {
canvas.restore();
}
_ => {}
}
}
fn root_component(&self, root: Element) -> Element {
BorderlessRoot {
thickness: self.thickness,
inner: root,
}
.into_element()
}
}
#[derive(Clone, PartialEq)]
struct BorderlessRoot {
thickness: f32,
inner: Element,
}
impl Component for BorderlessRoot {
fn render(&self) -> impl IntoElement {
rect()
.expanded()
.child(self.inner.clone())
.maybe(!cfg!(target_os = "macos"), |el| {
el.child(ResizeBands::new(self.thickness))
})
}
}
pub fn use_maximized() -> State<bool> {
let mut maximized = use_state(|| false);
use_side_effect(move || {
let _ = Platform::get().root_size.read();
Platform::get().with_window(Platform::window_id(), move |window| {
if let Some(mut maximized) = maximized.try_write() {
*maximized = window.fullscreen().is_some() || window.is_maximized();
}
});
});
maximized
}
#[derive(PartialEq)]
pub struct ResizeBands {
thickness: f32,
key: DiffKey,
}
impl KeyExt for ResizeBands {
fn write_key(&mut self) -> &mut DiffKey {
&mut self.key
}
}
impl ResizeBands {
pub fn new(thickness: f32) -> Self {
Self {
thickness,
key: DiffKey::None,
}
}
}
impl Component for ResizeBands {
fn render(&self) -> impl IntoElement {
let maximized = use_maximized();
let size = *Platform::get().root_size.read();
rect()
.layer(Layer::Overlay)
.width(Size::px(0.))
.height(Size::px(0.))
.maybe(!maximized(), |el| {
el.children(
DIRECTIONS
.iter()
.map(|direction| self.band(*direction, size)),
)
})
}
}
impl ResizeBands {
fn band(&self, direction: ResizeDirection, size: Size2D) -> Element {
let band = self.thickness;
let corner = band * 2.;
let span_x = (size.width - corner * 2.).max(0.);
let span_y = (size.height - corner * 2.).max(0.);
let far_x = (size.width - band).max(0.);
let far_y = (size.height - band).max(0.);
let corner_far_x = (size.width - corner).max(0.);
let corner_far_y = (size.height - corner).max(0.);
let (left, top, width, height) = match direction {
ResizeDirection::North => (corner, 0., span_x, band),
ResizeDirection::South => (corner, far_y, span_x, band),
ResizeDirection::West => (0., corner, band, span_y),
ResizeDirection::East => (far_x, corner, band, span_y),
ResizeDirection::NorthWest => (0., 0., corner, corner),
ResizeDirection::NorthEast => (corner_far_x, 0., corner, corner),
ResizeDirection::SouthWest => (0., corner_far_y, corner, corner),
ResizeDirection::SouthEast => (corner_far_x, corner_far_y, corner, corner),
};
let area = Area::new(Point2D::new(left, top), Size2D::new(width, height));
rect()
.position(
Position::new_global()
.top(area.origin.y)
.left(area.origin.x),
)
.width(Size::px(area.width()))
.height(Size::px(area.height()))
.cursor(cursor(direction))
.on_pointer_down(move |_| {
Platform::get().with_window(Platform::window_id(), move |window| {
let _ = window.drag_resize_window(direction);
});
})
.into_element()
}
}
fn cursor(direction: ResizeDirection) -> CursorIcon {
match direction {
ResizeDirection::North | ResizeDirection::South => CursorIcon::NsResize,
ResizeDirection::West | ResizeDirection::East => CursorIcon::EwResize,
ResizeDirection::NorthWest | ResizeDirection::SouthEast => CursorIcon::NwseResize,
ResizeDirection::NorthEast | ResizeDirection::SouthWest => CursorIcon::NeswResize,
}
}