Skip to content

Commit

Permalink
Add WindowDecorations widget.
Browse files Browse the repository at this point in the history
  • Loading branch information
melix99 committed Sep 22, 2024
1 parent c5a1f36 commit f79a70b
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 0 deletions.
54 changes: 54 additions & 0 deletions masonry/examples/custom_decorations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2024 the Xilem Authors and the Druid Authors
// SPDX-License-Identifier: Apache-2.0

// On Windows platform, don't show a console when opening the app.
#![windows_subsystem = "windows"]

use masonry::app_driver::{AppDriver, DriverCtx};
use masonry::dpi::LogicalSize;
use masonry::widget::{Button, CrossAxisAlignment, Flex, RootWidget, TitleBar, WindowDecorations};
use masonry::{Action, WidgetId};
use winit::window::Window;

struct Driver;

impl AppDriver for Driver {
fn on_action(&mut self, _ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
match action {
Action::ButtonPressed(_) => {
println!("Hello");
}
action => {
eprintln!("Unexpected action {action:?}");
}
}
}
}

pub fn main() {
let title_bar = TitleBar::new();

let button = Button::new("Say hello");

let content = Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Fill)
.with_child(title_bar)
.with_flex_child(button, CrossAxisAlignment::Center);

let main_widget = WindowDecorations::new(content);

let window_size = LogicalSize::new(400.0, 400.0);
let window_attributes = Window::default_attributes()
.with_title("Hello World!")
.with_resizable(true)
.with_decorations(false)
.with_min_inner_size(window_size);

masonry::event_loop_runner::run(
masonry::event_loop_runner::EventLoop::with_user_event(),
window_attributes,
RootWidget::new(main_widget),
Driver,
)
.unwrap();
}
2 changes: 2 additions & 0 deletions masonry/src/widget/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ mod title_bar;
mod variable_label;
mod widget_arena;
mod window_button;
mod window_decorations;
mod window_handle;

pub use self::image::Image;
Expand All @@ -58,6 +59,7 @@ pub use widget_mut::WidgetMut;
pub use widget_pod::WidgetPod;
pub use widget_ref::WidgetRef;
pub use widget_state::WidgetState;
pub use window_decorations::WindowDecorations;
pub use window_handle::WindowHandle;

pub(crate) use widget_arena::WidgetArena;
Expand Down
113 changes: 113 additions & 0 deletions masonry/src/widget/window_decorations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0

//! A container to show window decorations.
use accesskit::Role;
use cursor_icon::CursorIcon;
use smallvec::{smallvec, SmallVec};
use tracing::{trace_span, Span};
use vello::kurbo::Insets;
use vello::Scene;
use winit::window::ResizeDirection;

use crate::paint_scene_helpers::stroke;
use crate::widget::WidgetPod;
use crate::{
theme, AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx,
PaintCtx, PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId,
};

const BORDER_WIDTH: f64 = 2.0;
const INSETS: Insets = Insets::uniform(BORDER_WIDTH);

/// A container to show window decorations.
pub struct WindowDecorations<W> {
pub(crate) child: WidgetPod<W>,
}

impl<W: Widget> WindowDecorations<W> {
pub fn new(widget: W) -> WindowDecorations<W> {
WindowDecorations {
child: WidgetPod::new(widget),
}
}
}

impl<W: Widget> Widget for WindowDecorations<W> {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent) {
match event {
PointerEvent::PointerMove(state) => {
if state.position.y <= BORDER_WIDTH {
ctx.set_cursor(&CursorIcon::NResize);
} else if state.position.y >= ctx.size().height - BORDER_WIDTH {
ctx.set_cursor(&CursorIcon::SResize);
} else if state.position.x <= BORDER_WIDTH {
ctx.set_cursor(&CursorIcon::WResize);
} else if state.position.x >= ctx.size().width - BORDER_WIDTH {
ctx.set_cursor(&CursorIcon::EResize);
}
}
PointerEvent::PointerLeave(_) => {
ctx.set_cursor(&CursorIcon::Default);
}
PointerEvent::PointerDown(_, state) => {
if state.position.y <= BORDER_WIDTH {
ctx.drag_resize_window(ResizeDirection::North);
} else if state.position.y >= ctx.size().height - BORDER_WIDTH {
ctx.drag_resize_window(ResizeDirection::South);
} else if state.position.x <= BORDER_WIDTH {
ctx.drag_resize_window(ResizeDirection::West);
} else if state.position.x >= ctx.size().width - BORDER_WIDTH {
ctx.drag_resize_window(ResizeDirection::East);
}
}
_ => {}
}
}

fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}

fn on_status_change(&mut self, _: &mut LifeCycleCtx, _: &StatusChange) {}

fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
self.child.lifecycle(ctx, event);
}

fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
let padding = Size::new(INSETS.x_value(), INSETS.y_value());
let child_bc = bc.shrink(padding);

let child_size = ctx.run_layout(&mut self.child, &child_bc);

let size = bc.constrain(Size::new(
child_size.width + padding.width,
child_size.height + padding.height,
));

let child_offset = (size.to_vec2() - child_size.to_vec2()) / 2.0;
ctx.place_child(&mut self.child, child_offset.to_point());

size
}

fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
let rect = ctx.size().to_rect().inset(-BORDER_WIDTH / 2.0);
stroke(scene, &rect, theme::BORDER_DARK, BORDER_WIDTH);
}

fn accessibility_role(&self) -> Role {
Role::GenericContainer
}

fn accessibility(&mut self, _ctx: &mut AccessCtx) {}

fn children_ids(&self) -> SmallVec<[WidgetId; 16]> {
smallvec![self.child.id()]
}

fn make_trace_span(&self) -> Span {
trace_span!("WindowDecorations")
}
}

0 comments on commit f79a70b

Please sign in to comment.