use crate::style::StyledProperty;
use crate::{
border::BorderBuilder,
brush::Brush,
button::{ButtonBuilder, ButtonMessage},
core::{
algebra::Vector2, color::Color, math::Rect, pool::Handle, reflect::prelude::*,
type_traits::prelude::*, uuid_provider, visitor::prelude::*,
},
decorator::DecoratorBuilder,
define_constructor,
font::FontResource,
grid::{Column, GridBuilder, Row},
message::{CursorIcon, KeyCode, MessageDirection, UiMessage},
navigation::NavigationLayerBuilder,
style::resource::StyleResourceExt,
style::Style,
text::{Text, TextBuilder, TextMessage},
vector_image::{Primitive, VectorImageBuilder},
widget::{Widget, WidgetBuilder, WidgetMessage},
BuildContext, Control, HorizontalAlignment, RestrictionEntry, Thickness, UiNode, UserInterface,
VerticalAlignment,
};
use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
use fyrox_graph::{BaseSceneGraph, SceneGraph};
use std::{
cell::RefCell,
ops::{Deref, DerefMut},
};
#[derive(Debug, Clone, PartialEq)]
pub enum WindowMessage {
Open {
center: bool,
focus_content: bool,
},
OpenAt {
position: Vector2<f32>,
focus_content: bool,
},
OpenAndAlign {
relative_to: Handle<UiNode>,
horizontal_alignment: HorizontalAlignment,
vertical_alignment: VerticalAlignment,
margin: Thickness,
modal: bool,
focus_content: bool,
},
OpenModal {
center: bool,
focus_content: bool,
},
Close,
Minimize(bool),
Maximize,
CanMinimize(bool),
CanClose(bool),
CanResize(bool),
MoveStart,
Move(Vector2<f32>),
MoveEnd,
Title(WindowTitle),
SafeBorderSize(Option<Vector2<f32>>),
}
impl WindowMessage {
define_constructor!(
WindowMessage:Open => fn open(center: bool, focus_content: bool), layout: false
);
define_constructor!(
WindowMessage:OpenAt => fn open_at(position: Vector2<f32>, focus_content: bool), layout: false
);
define_constructor!(
WindowMessage:OpenAndAlign => fn open_and_align(
relative_to: Handle<UiNode>,
horizontal_alignment: HorizontalAlignment,
vertical_alignment: VerticalAlignment,
margin: Thickness,
modal: bool,
focus_content: bool
), layout: false
);
define_constructor!(
WindowMessage:OpenModal => fn open_modal(center: bool, focus_content: bool), layout: false
);
define_constructor!(
WindowMessage:Close => fn close(), layout: false
);
define_constructor!(
WindowMessage:Minimize => fn minimize(bool), layout: false
);
define_constructor!(
WindowMessage:Maximize => fn maximize(), layout: false
);
define_constructor!(
WindowMessage:CanMinimize => fn can_minimize(bool), layout: false
);
define_constructor!(
WindowMessage:CanClose => fn can_close(bool), layout: false
);
define_constructor!(
WindowMessage:CanResize => fn can_resize(bool), layout: false
);
define_constructor!(
WindowMessage:MoveStart => fn move_start(), layout: false
);
define_constructor!(
WindowMessage:Move => fn move_to(Vector2<f32>), layout: false
);
define_constructor!(
WindowMessage:MoveEnd => fn move_end(), layout: false
);
define_constructor!(
WindowMessage:Title => fn title(WindowTitle), layout: false
);
define_constructor!(
WindowMessage:SafeBorderSize => fn safe_border_size(Option<Vector2<f32>>), layout: false
);
}
#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
pub struct Window {
pub widget: Widget,
pub mouse_click_pos: Vector2<f32>,
pub initial_position: Vector2<f32>,
pub initial_size: Vector2<f32>,
pub is_dragging: bool,
pub minimized: bool,
pub can_minimize: bool,
pub can_maximize: bool,
pub can_close: bool,
pub can_resize: bool,
pub header: Handle<UiNode>,
pub minimize_button: Handle<UiNode>,
pub maximize_button: Handle<UiNode>,
pub close_button: Handle<UiNode>,
pub drag_delta: Vector2<f32>,
pub content: Handle<UiNode>,
pub grips: RefCell<[Grip; 8]>,
pub title: Handle<UiNode>,
pub title_grid: Handle<UiNode>,
pub safe_border_size: Option<Vector2<f32>>,
pub prev_bounds: Option<Rect<f32>>,
#[visit(optional)] pub close_by_esc: bool,
#[visit(optional)] pub remove_on_close: bool,
}
impl ConstructorProvider<UiNode, UserInterface> for Window {
fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
GraphNodeConstructor::new::<Self>()
.with_variant("Window", |ui| {
WindowBuilder::new(WidgetBuilder::new().with_name("Window"))
.build(&mut ui.build_ctx())
.into()
})
.with_group("Layout")
}
}
const GRIP_SIZE: f32 = 6.0;
const CORNER_GRIP_SIZE: f32 = GRIP_SIZE * 2.0;
#[derive(Copy, Clone, Debug, Visit, Reflect, Default)]
pub enum GripKind {
#[default]
LeftTopCorner = 0,
RightTopCorner = 1,
RightBottomCorner = 2,
LeftBottomCorner = 3,
Left = 4,
Top = 5,
Right = 6,
Bottom = 7,
}
#[derive(Clone, Visit, Default, Debug, Reflect)]
pub struct Grip {
pub kind: GripKind,
pub bounds: Rect<f32>,
pub is_dragging: bool,
pub cursor: CursorIcon,
}
impl Grip {
fn new(kind: GripKind, cursor: CursorIcon) -> Self {
Self {
kind,
bounds: Default::default(),
is_dragging: false,
cursor,
}
}
}
crate::define_widget_deref!(Window);
uuid_provider!(Window = "9331bf32-8614-4005-874c-5239e56bb15e");
impl Control for Window {
fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
let size = self.widget.arrange_override(ui, final_size);
let mut grips = self.grips.borrow_mut();
grips[GripKind::Left as usize].bounds =
Rect::new(0.0, GRIP_SIZE, GRIP_SIZE, final_size.y - GRIP_SIZE * 2.0);
grips[GripKind::Top as usize].bounds =
Rect::new(GRIP_SIZE, 0.0, final_size.x - GRIP_SIZE * 2.0, GRIP_SIZE);
grips[GripKind::Right as usize].bounds = Rect::new(
final_size.x - GRIP_SIZE,
GRIP_SIZE,
GRIP_SIZE,
final_size.y - GRIP_SIZE * 2.0,
);
grips[GripKind::Bottom as usize].bounds = Rect::new(
GRIP_SIZE,
final_size.y - GRIP_SIZE,
final_size.x - GRIP_SIZE * 2.0,
GRIP_SIZE,
);
grips[GripKind::LeftTopCorner as usize].bounds =
Rect::new(0.0, 0.0, CORNER_GRIP_SIZE, CORNER_GRIP_SIZE);
grips[GripKind::RightTopCorner as usize].bounds = Rect::new(
final_size.x - GRIP_SIZE,
0.0,
CORNER_GRIP_SIZE,
CORNER_GRIP_SIZE,
);
grips[GripKind::RightBottomCorner as usize].bounds = Rect::new(
final_size.x - CORNER_GRIP_SIZE,
final_size.y - CORNER_GRIP_SIZE,
CORNER_GRIP_SIZE,
CORNER_GRIP_SIZE,
);
grips[GripKind::LeftBottomCorner as usize].bounds = Rect::new(
0.0,
final_size.y - CORNER_GRIP_SIZE,
CORNER_GRIP_SIZE,
CORNER_GRIP_SIZE,
);
size
}
fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
self.widget.handle_routed_message(ui, message);
if let Some(msg) = message.data::<WidgetMessage>() {
if self.can_resize && !self.is_dragging {
match msg {
&WidgetMessage::MouseDown { pos, .. } => {
ui.send_message(WidgetMessage::topmost(
self.handle(),
MessageDirection::ToWidget,
));
for grip in self.grips.borrow_mut().iter_mut() {
let screen_bounds = grip.bounds.transform(&self.visual_transform);
if screen_bounds.contains(pos) {
grip.is_dragging = true;
self.initial_position = self.screen_position();
self.initial_size = self.actual_local_size();
self.mouse_click_pos = pos;
ui.capture_mouse(self.handle());
break;
}
}
}
WidgetMessage::MouseUp { .. } => {
for grip in self.grips.borrow_mut().iter_mut() {
if grip.is_dragging {
ui.release_mouse_capture();
grip.is_dragging = false;
break;
}
}
}
&WidgetMessage::MouseMove { pos, .. } => {
let mut new_cursor = None;
for grip in self.grips.borrow().iter() {
let screen_bounds = grip.bounds.transform(&self.visual_transform);
if grip.is_dragging || screen_bounds.contains(pos) {
new_cursor = Some(grip.cursor);
}
if grip.is_dragging {
let delta = self.mouse_click_pos - pos;
let (dx, dy, dw, dh) = match grip.kind {
GripKind::Left => (-1.0, 0.0, 1.0, 0.0),
GripKind::Top => (0.0, -1.0, 0.0, 1.0),
GripKind::Right => (0.0, 0.0, -1.0, 0.0),
GripKind::Bottom => (0.0, 0.0, 0.0, -1.0),
GripKind::LeftTopCorner => (-1.0, -1.0, 1.0, 1.0),
GripKind::RightTopCorner => (0.0, -1.0, -1.0, 1.0),
GripKind::RightBottomCorner => (0.0, 0.0, -1.0, -1.0),
GripKind::LeftBottomCorner => (-1.0, 0.0, 1.0, -1.0),
};
let new_pos = self.initial_position
+ Vector2::new(delta.x * dx, delta.y * dy);
let new_size =
self.initial_size + Vector2::new(delta.x * dw, delta.y * dh);
if new_size.x > self.min_width()
&& new_size.x < self.max_width()
&& new_size.y > self.min_height()
&& new_size.y < self.max_height()
{
ui.send_message(WidgetMessage::desired_position(
self.handle(),
MessageDirection::ToWidget,
ui.screen_to_root_canvas_space(new_pos),
));
ui.send_message(WidgetMessage::width(
self.handle(),
MessageDirection::ToWidget,
new_size.x,
));
ui.send_message(WidgetMessage::height(
self.handle(),
MessageDirection::ToWidget,
new_size.y,
));
}
break;
}
}
self.set_cursor(new_cursor);
}
_ => {}
}
} else {
self.set_cursor(None);
}
if (message.destination() == self.header
|| ui
.node(self.header)
.has_descendant(message.destination(), ui))
&& !message.handled()
&& !self.has_active_grip()
{
match msg {
WidgetMessage::MouseDown { pos, .. } => {
self.mouse_click_pos = *pos;
ui.send_message(WindowMessage::move_start(
self.handle,
MessageDirection::ToWidget,
));
message.set_handled(true);
}
WidgetMessage::MouseUp { .. } => {
ui.send_message(WindowMessage::move_end(
self.handle,
MessageDirection::ToWidget,
));
message.set_handled(true);
}
WidgetMessage::MouseMove { pos, .. } => {
if self.is_dragging {
self.drag_delta = *pos - self.mouse_click_pos;
let new_pos = self.initial_position + self.drag_delta;
ui.send_message(WindowMessage::move_to(
self.handle(),
MessageDirection::ToWidget,
ui.screen_to_root_canvas_space(new_pos),
));
}
message.set_handled(true);
}
_ => (),
}
}
match msg {
WidgetMessage::Unlink => {
if message.destination() == self.handle() {
self.initial_position = self.screen_position();
}
}
WidgetMessage::KeyDown(key_code)
if self.close_by_esc
&& !self.is_docked(ui)
&& self.can_close
&& *key_code == KeyCode::Escape
&& !message.handled() =>
{
ui.send_message(WindowMessage::close(
self.handle,
MessageDirection::ToWidget,
));
message.set_handled(true);
}
_ => {}
}
} else if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
if message.destination() == self.minimize_button {
ui.send_message(WindowMessage::minimize(
self.handle(),
MessageDirection::ToWidget,
!self.minimized,
));
} else if message.destination() == self.maximize_button {
ui.send_message(WindowMessage::maximize(
self.handle(),
MessageDirection::ToWidget,
));
} else if message.destination() == self.close_button {
ui.send_message(WindowMessage::close(
self.handle(),
MessageDirection::ToWidget,
));
}
} else if let Some(msg) = message.data::<WindowMessage>() {
if message.destination() == self.handle()
&& message.direction() == MessageDirection::ToWidget
{
match msg {
&WindowMessage::Open {
center,
focus_content,
} => {
if !self.visibility() {
ui.send_message(WidgetMessage::visibility(
self.handle(),
MessageDirection::ToWidget,
true,
));
ui.send_message(WidgetMessage::topmost(
self.handle(),
MessageDirection::ToWidget,
));
if center {
ui.send_message(WidgetMessage::center(
self.handle(),
MessageDirection::ToWidget,
));
}
if focus_content {
ui.send_message(WidgetMessage::focus(
self.content_to_focus(),
MessageDirection::ToWidget,
));
}
}
}
&WindowMessage::OpenAt {
position,
focus_content,
} => {
if !self.visibility() {
ui.send_message(WidgetMessage::visibility(
self.handle(),
MessageDirection::ToWidget,
true,
));
ui.send_message(WidgetMessage::topmost(
self.handle(),
MessageDirection::ToWidget,
));
ui.send_message(WidgetMessage::desired_position(
self.handle(),
MessageDirection::ToWidget,
position,
));
if focus_content {
ui.send_message(WidgetMessage::focus(
self.content_to_focus(),
MessageDirection::ToWidget,
));
}
}
}
&WindowMessage::OpenAndAlign {
relative_to,
horizontal_alignment,
vertical_alignment,
margin,
modal,
focus_content,
} => {
if !self.visibility() {
ui.send_message(WidgetMessage::visibility(
self.handle(),
MessageDirection::ToWidget,
true,
));
ui.send_message(WidgetMessage::topmost(
self.handle(),
MessageDirection::ToWidget,
));
ui.send_message(WidgetMessage::align(
self.handle(),
MessageDirection::ToWidget,
relative_to,
horizontal_alignment,
vertical_alignment,
margin,
));
if modal {
ui.push_picking_restriction(RestrictionEntry {
handle: self.handle(),
stop: true,
});
}
if focus_content {
ui.send_message(WidgetMessage::focus(
self.content_to_focus(),
MessageDirection::ToWidget,
));
}
}
}
&WindowMessage::OpenModal {
center,
focus_content,
} => {
if !self.visibility() {
ui.send_message(WidgetMessage::visibility(
self.handle(),
MessageDirection::ToWidget,
true,
));
ui.send_message(WidgetMessage::topmost(
self.handle(),
MessageDirection::ToWidget,
));
if center {
ui.send_message(WidgetMessage::center(
self.handle(),
MessageDirection::ToWidget,
));
}
ui.push_picking_restriction(RestrictionEntry {
handle: self.handle(),
stop: true,
});
if focus_content {
ui.send_message(WidgetMessage::focus(
self.content_to_focus(),
MessageDirection::ToWidget,
));
}
}
}
WindowMessage::Close => {
if self.visibility() {
ui.send_message(WidgetMessage::visibility(
self.handle(),
MessageDirection::ToWidget,
false,
));
ui.remove_picking_restriction(self.handle());
if self.remove_on_close {
ui.send_message(WidgetMessage::remove(
self.handle,
MessageDirection::ToWidget,
));
}
}
}
&WindowMessage::Minimize(minimized) => {
if self.minimized != minimized {
self.minimized = minimized;
if self.content.is_some() {
ui.send_message(WidgetMessage::visibility(
self.content,
MessageDirection::ToWidget,
!minimized,
));
}
}
}
WindowMessage::Maximize => {
let current_size = self.actual_local_size();
let current_position = self.actual_local_position();
let new_bounds = self
.prev_bounds
.replace(Rect::new(
current_position.x,
current_position.y,
current_size.x,
current_size.y,
))
.unwrap_or_else(|| {
Rect::new(0.0, 0.0, ui.screen_size.x, ui.screen_size.y)
});
ui.send_message(WidgetMessage::desired_position(
self.handle,
MessageDirection::ToWidget,
new_bounds.position,
));
ui.send_message(WidgetMessage::width(
self.handle,
MessageDirection::ToWidget,
new_bounds.w(),
));
ui.send_message(WidgetMessage::height(
self.handle,
MessageDirection::ToWidget,
new_bounds.h(),
));
}
&WindowMessage::CanMinimize(value) => {
if self.can_minimize != value {
self.can_minimize = value;
if self.minimize_button.is_some() {
ui.send_message(WidgetMessage::visibility(
self.minimize_button,
MessageDirection::ToWidget,
value,
));
}
}
}
&WindowMessage::CanClose(value) => {
if self.can_close != value {
self.can_close = value;
if self.close_button.is_some() {
ui.send_message(WidgetMessage::visibility(
self.close_button,
MessageDirection::ToWidget,
value,
));
}
}
}
&WindowMessage::CanResize(value) => {
if self.can_resize != value {
self.can_resize = value;
ui.send_message(message.reverse());
}
}
&WindowMessage::Move(mut new_pos) => {
if let Some(safe_border) = self.safe_border_size {
new_pos.x = new_pos.x.clamp(
-(self.actual_local_size().x - safe_border.x).abs(),
(ui.screen_size().x - safe_border.x).abs(),
);
new_pos.y = new_pos
.y
.clamp(0.0, (ui.screen_size().y - safe_border.y).abs());
}
if self.is_dragging && self.desired_local_position() != new_pos {
ui.send_message(WidgetMessage::desired_position(
self.handle(),
MessageDirection::ToWidget,
new_pos,
));
ui.send_message(message.reverse());
}
}
WindowMessage::MoveStart => {
if !self.is_dragging {
ui.capture_mouse(self.header);
let initial_position = self.screen_position();
self.initial_position = initial_position;
self.is_dragging = true;
if let Some(prev_bounds) = self.prev_bounds.take() {
ui.send_message(WidgetMessage::width(
self.handle,
MessageDirection::ToWidget,
prev_bounds.w(),
));
ui.send_message(WidgetMessage::height(
self.handle,
MessageDirection::ToWidget,
prev_bounds.h(),
));
}
ui.send_message(message.reverse());
}
}
WindowMessage::MoveEnd => {
if self.is_dragging {
ui.release_mouse_capture();
self.is_dragging = false;
ui.send_message(message.reverse());
}
}
WindowMessage::Title(title) => {
match title {
WindowTitle::Text {
text,
font,
font_size,
} => {
if ui.try_get_of_type::<Text>(self.title).is_some() {
ui.send_message(TextMessage::text(
self.title,
MessageDirection::ToWidget,
text.clone(),
));
if let Some(font) = font {
ui.send_message(TextMessage::font(
self.title,
MessageDirection::ToWidget,
font.clone(),
))
}
if let Some(font_size) = font_size {
ui.send_message(TextMessage::font_size(
self.title,
MessageDirection::ToWidget,
font_size.clone(),
));
}
} else {
ui.send_message(WidgetMessage::remove(
self.title,
MessageDirection::ToWidget,
));
let font =
font.clone().unwrap_or_else(|| ui.default_font.clone());
let ctx = &mut ui.build_ctx();
self.title = make_text_title(
ctx,
text,
font,
font_size.clone().unwrap_or_else(|| {
ctx.style.property(Style::FONT_SIZE)
}),
);
ui.send_message(WidgetMessage::link(
self.title,
MessageDirection::ToWidget,
self.title_grid,
));
}
}
WindowTitle::Node(node) => {
if self.title.is_some() {
ui.send_message(WidgetMessage::remove(
self.title,
MessageDirection::ToWidget,
));
}
if node.is_some() {
self.title = *node;
ui.send_message(WidgetMessage::link(
self.title,
MessageDirection::ToWidget,
self.title_grid,
));
}
}
}
}
WindowMessage::SafeBorderSize(size) => {
if &self.safe_border_size != size {
self.safe_border_size = *size;
ui.send_message(message.reverse());
}
}
}
}
}
}
}
impl Window {
pub fn has_active_grip(&self) -> bool {
for grip in self.grips.borrow().iter() {
if grip.is_dragging {
return true;
}
}
false
}
fn content_to_focus(&self) -> Handle<UiNode> {
if self.content.is_some() {
self.content
} else {
self.handle
}
}
fn is_docked(&self, ui: &UserInterface) -> bool {
self.parent() != ui.root_canvas
}
}
pub struct WindowBuilder {
pub widget_builder: WidgetBuilder,
pub content: Handle<UiNode>,
pub title: Option<WindowTitle>,
pub can_close: bool,
pub can_minimize: bool,
pub can_maximize: bool,
pub open: bool,
pub close_button: Option<Handle<UiNode>>,
pub minimize_button: Option<Handle<UiNode>>,
pub maximize_button: Option<Handle<UiNode>>,
pub modal: bool,
pub can_resize: bool,
pub safe_border_size: Option<Vector2<f32>>,
pub close_by_esc: bool,
pub remove_on_close: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum WindowTitle {
Text {
text: String,
font: Option<FontResource>,
font_size: Option<StyledProperty<f32>>,
},
Node(Handle<UiNode>),
}
impl WindowTitle {
pub fn text<P: AsRef<str>>(text: P) -> Self {
WindowTitle::Text {
text: text.as_ref().to_owned(),
font: None,
font_size: None,
}
}
pub fn text_with_font<P: AsRef<str>>(text: P, font: FontResource) -> Self {
WindowTitle::Text {
text: text.as_ref().to_owned(),
font: Some(font),
font_size: None,
}
}
pub fn text_with_font_size<P: AsRef<str>>(
text: P,
font: FontResource,
size: StyledProperty<f32>,
) -> Self {
WindowTitle::Text {
text: text.as_ref().to_owned(),
font: Some(font),
font_size: Some(size),
}
}
pub fn node(node: Handle<UiNode>) -> Self {
Self::Node(node)
}
}
fn make_text_title(
ctx: &mut BuildContext,
text: &str,
font: FontResource,
size: StyledProperty<f32>,
) -> Handle<UiNode> {
TextBuilder::new(
WidgetBuilder::new()
.with_margin(Thickness::left(5.0))
.on_row(0)
.on_column(0),
)
.with_font_size(size)
.with_font(font)
.with_vertical_text_alignment(VerticalAlignment::Center)
.with_horizontal_text_alignment(HorizontalAlignment::Left)
.with_text(text)
.build(ctx)
}
enum HeaderButton {
Close,
Minimize,
Maximize,
}
fn make_mark(ctx: &mut BuildContext, button: HeaderButton) -> Handle<UiNode> {
let size = 12.0;
VectorImageBuilder::new(
WidgetBuilder::new()
.with_horizontal_alignment(HorizontalAlignment::Center)
.with_vertical_alignment(match button {
HeaderButton::Close => VerticalAlignment::Center,
HeaderButton::Minimize => VerticalAlignment::Bottom,
HeaderButton::Maximize => VerticalAlignment::Center,
})
.with_width(size)
.with_height(size)
.with_foreground(ctx.style.property(Style::BRUSH_BRIGHT)),
)
.with_primitives(match button {
HeaderButton::Close => {
vec![
Primitive::Line {
begin: Vector2::new(0.0, 0.0),
end: Vector2::new(size, size),
thickness: 1.0,
},
Primitive::Line {
begin: Vector2::new(size, 0.0),
end: Vector2::new(0.0, size),
thickness: 1.0,
},
]
}
HeaderButton::Minimize => {
let bottom_spacing = 3.0;
vec![Primitive::Line {
begin: Vector2::new(0.0, size - bottom_spacing),
end: Vector2::new(size, size - bottom_spacing),
thickness: 1.0,
}]
}
HeaderButton::Maximize => {
let thickness = 1.25;
let half_thickness = thickness * 0.5;
vec![
Primitive::Line {
begin: Vector2::new(0.0, half_thickness),
end: Vector2::new(size, half_thickness),
thickness,
},
Primitive::Line {
begin: Vector2::new(size - half_thickness, 0.0),
end: Vector2::new(size - half_thickness, size),
thickness,
},
Primitive::Line {
begin: Vector2::new(size, size - half_thickness),
end: Vector2::new(0.0, size - half_thickness),
thickness,
},
Primitive::Line {
begin: Vector2::new(half_thickness, size),
end: Vector2::new(half_thickness, 0.0),
thickness,
},
]
}
})
.build(ctx)
}
fn make_header_button(ctx: &mut BuildContext, button: HeaderButton) -> Handle<UiNode> {
ButtonBuilder::new(WidgetBuilder::new().with_margin(Thickness::uniform(2.0)))
.with_back(
DecoratorBuilder::new(
BorderBuilder::new(WidgetBuilder::new())
.with_stroke_thickness(Thickness::uniform(0.0).into())
.with_pad_by_corner_radius(false)
.with_corner_radius(4.0f32.into()),
)
.with_normal_brush(Brush::Solid(Color::TRANSPARENT).into())
.with_hover_brush(ctx.style.property(Style::BRUSH_LIGHT))
.with_pressed_brush(ctx.style.property(Style::BRUSH_LIGHTEST))
.build(ctx),
)
.with_content(make_mark(ctx, button))
.build(ctx)
}
impl WindowBuilder {
pub fn new(widget_builder: WidgetBuilder) -> Self {
Self {
widget_builder,
content: Handle::NONE,
title: None,
can_close: true,
can_minimize: true,
can_maximize: true,
open: true,
close_button: None,
minimize_button: None,
maximize_button: None,
modal: false,
can_resize: true,
safe_border_size: Some(Vector2::new(25.0, 20.0)),
close_by_esc: true,
remove_on_close: false,
}
}
pub fn with_content(mut self, content: Handle<UiNode>) -> Self {
self.content = content;
self
}
pub fn with_title(mut self, title: WindowTitle) -> Self {
self.title = Some(title);
self
}
pub fn with_minimize_button(mut self, button: Handle<UiNode>) -> Self {
self.minimize_button = Some(button);
self
}
pub fn with_maximize_button(mut self, button: Handle<UiNode>) -> Self {
self.minimize_button = Some(button);
self
}
pub fn with_close_button(mut self, button: Handle<UiNode>) -> Self {
self.close_button = Some(button);
self
}
pub fn can_close(mut self, can_close: bool) -> Self {
self.can_close = can_close;
self
}
pub fn can_minimize(mut self, can_minimize: bool) -> Self {
self.can_minimize = can_minimize;
self
}
pub fn can_maximize(mut self, can_minimize: bool) -> Self {
self.can_maximize = can_minimize;
self
}
pub fn open(mut self, open: bool) -> Self {
self.open = open;
self
}
pub fn modal(mut self, modal: bool) -> Self {
self.modal = modal;
self
}
pub fn can_resize(mut self, can_resize: bool) -> Self {
self.can_resize = can_resize;
self
}
pub fn with_safe_border_size(mut self, size: Option<Vector2<f32>>) -> Self {
self.safe_border_size = size.map(|s| Vector2::new(s.x.abs(), s.y.abs()));
self
}
pub fn with_close_by_esc(mut self, close: bool) -> Self {
self.close_by_esc = close;
self
}
pub fn with_remove_on_close(mut self, close: bool) -> Self {
self.remove_on_close = close;
self
}
pub fn build_window(self, ctx: &mut BuildContext) -> Window {
let minimize_button;
let maximize_button;
let close_button;
let title;
let title_grid;
let header = BorderBuilder::new(
WidgetBuilder::new()
.with_horizontal_alignment(HorizontalAlignment::Stretch)
.with_height(22.0)
.with_background(ctx.style.property(Style::BRUSH_DARKER))
.with_child({
title_grid = GridBuilder::new(
WidgetBuilder::new()
.with_child({
title = match self.title {
None => Handle::NONE,
Some(window_title) => match window_title {
WindowTitle::Node(node) => node,
WindowTitle::Text {
text,
font,
font_size,
} => make_text_title(
ctx,
&text,
font.unwrap_or_else(|| ctx.default_font()),
font_size.unwrap_or_else(|| {
ctx.style.property(Style::FONT_SIZE)
}),
),
},
};
title
})
.with_child({
minimize_button = self.minimize_button.unwrap_or_else(|| {
make_header_button(ctx, HeaderButton::Minimize)
});
ctx[minimize_button]
.set_visibility(self.can_minimize)
.set_width(20.0)
.set_row(0)
.set_column(1);
minimize_button
})
.with_child({
maximize_button = self.maximize_button.unwrap_or_else(|| {
make_header_button(ctx, HeaderButton::Maximize)
});
ctx[maximize_button]
.set_visibility(self.can_maximize)
.set_width(20.0)
.set_row(0)
.set_column(2);
maximize_button
})
.with_child({
close_button = self.close_button.unwrap_or_else(|| {
make_header_button(ctx, HeaderButton::Close)
});
ctx[close_button]
.set_width(20.0)
.set_visibility(self.can_close)
.set_row(0)
.set_column(3);
close_button
}),
)
.add_column(Column::stretch())
.add_column(Column::auto())
.add_column(Column::auto())
.add_column(Column::auto())
.add_row(Row::stretch())
.build(ctx);
title_grid
})
.on_row(0),
)
.with_pad_by_corner_radius(false)
.with_corner_radius(4.0f32.into())
.with_stroke_thickness(Thickness::uniform(0.0).into())
.build(ctx);
let border = BorderBuilder::new(
WidgetBuilder::new()
.with_foreground(ctx.style.property(Style::BRUSH_DARKER))
.with_child(
GridBuilder::new(
WidgetBuilder::new()
.with_child(
NavigationLayerBuilder::new(
WidgetBuilder::new().on_row(1).with_child(self.content),
)
.build(ctx),
)
.with_child(header),
)
.add_column(Column::stretch())
.add_row(Row::auto())
.add_row(Row::stretch())
.build(ctx),
),
)
.with_pad_by_corner_radius(false)
.with_corner_radius(4.0f32.into())
.with_stroke_thickness(Thickness::uniform(1.0).into())
.build(ctx);
Window {
widget: self
.widget_builder
.with_visibility(self.open)
.with_child(border)
.build(ctx),
mouse_click_pos: Vector2::default(),
initial_position: Vector2::default(),
initial_size: Default::default(),
is_dragging: false,
minimized: false,
can_minimize: self.can_minimize,
can_maximize: self.can_maximize,
can_close: self.can_close,
can_resize: self.can_resize,
header,
minimize_button,
maximize_button,
close_button,
drag_delta: Default::default(),
content: self.content,
safe_border_size: self.safe_border_size,
grips: RefCell::new([
Grip::new(GripKind::LeftTopCorner, CursorIcon::NwResize),
Grip::new(GripKind::RightTopCorner, CursorIcon::NeResize),
Grip::new(GripKind::RightBottomCorner, CursorIcon::SeResize),
Grip::new(GripKind::LeftBottomCorner, CursorIcon::SwResize),
Grip::new(GripKind::Left, CursorIcon::WResize),
Grip::new(GripKind::Top, CursorIcon::NResize),
Grip::new(GripKind::Right, CursorIcon::EResize),
Grip::new(GripKind::Bottom, CursorIcon::SResize),
]),
title,
title_grid,
prev_bounds: None,
close_by_esc: self.close_by_esc,
remove_on_close: self.remove_on_close,
}
}
pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
let modal = self.modal;
let open = self.open;
let node = self.build_window(ctx);
let handle = ctx.add_node(UiNode::new(node));
if modal && open {
ctx.push_picking_restriction(RestrictionEntry { handle, stop: true });
}
handle
}
}
#[cfg(test)]
mod test {
use crate::window::WindowBuilder;
use crate::{test::test_widget_deletion, widget::WidgetBuilder};
#[test]
fn test_deletion() {
test_widget_deletion(|ctx| WindowBuilder::new(WidgetBuilder::new()).build(ctx));
}
}