#![warn(missing_docs)]
use crate::{
button::{ButtonBuilder, ButtonMessage},
core::{
algebra::Vector2, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
visitor::prelude::*,
},
define_constructor,
draw::DrawingContext,
formatted_text::WrapMode,
grid::{Column, GridBuilder, Row},
message::{MessageDirection, OsEvent, UiMessage},
stack_panel::StackPanelBuilder,
text::{TextBuilder, TextMessage},
widget::{Widget, WidgetBuilder},
window::{Window, WindowBuilder, WindowMessage, WindowTitle},
BuildContext, Control, HorizontalAlignment, Orientation, RestrictionEntry, Thickness, UiNode,
UserInterface,
};
use fyrox_core::uuid_provider;
use fyrox_core::variable::InheritableVariable;
use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
use std::ops::{Deref, DerefMut};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MessageBoxMessage {
Open {
title: Option<String>,
text: Option<String>,
},
Close(MessageBoxResult),
}
impl MessageBoxMessage {
define_constructor!(
MessageBoxMessage:Open => fn open(title: Option<String>, text: Option<String>), layout: false
);
define_constructor!(
MessageBoxMessage:Close => fn close(MessageBoxResult), layout: false
);
}
#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash, Debug)]
pub enum MessageBoxResult {
Ok,
No,
Yes,
Cancel,
}
#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash, Debug, Visit, Reflect, Default)]
pub enum MessageBoxButtons {
#[default]
Ok,
YesNo,
YesNoCancel,
}
#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
pub struct MessageBox {
#[component(include)]
pub window: Window,
pub buttons: InheritableVariable<MessageBoxButtons>,
pub ok_yes: InheritableVariable<Handle<UiNode>>,
pub no: InheritableVariable<Handle<UiNode>>,
pub cancel: InheritableVariable<Handle<UiNode>>,
pub text: InheritableVariable<Handle<UiNode>>,
}
impl ConstructorProvider<UiNode, UserInterface> for MessageBox {
fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
GraphNodeConstructor::new::<Self>()
.with_variant("Message Box", |ui| {
MessageBoxBuilder::new(WindowBuilder::new(
WidgetBuilder::new().with_name("Message Box"),
))
.build(&mut ui.build_ctx())
.into()
})
.with_group("Input")
}
}
impl Deref for MessageBox {
type Target = Widget;
fn deref(&self) -> &Self::Target {
&self.window
}
}
impl DerefMut for MessageBox {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.window
}
}
uuid_provider!(MessageBox = "b14c0012-4383-45cf-b9a1-231415d95373");
impl Control for MessageBox {
fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
self.window.measure_override(ui, available_size)
}
fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
self.window.arrange_override(ui, final_size)
}
fn draw(&self, drawing_context: &mut DrawingContext) {
self.window.draw(drawing_context)
}
fn update(&mut self, dt: f32, ui: &mut UserInterface) {
self.window.update(dt, ui);
}
fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
self.window.handle_routed_message(ui, message);
if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
if message.destination() == *self.ok_yes {
let result = match *self.buttons {
MessageBoxButtons::Ok => MessageBoxResult::Ok,
MessageBoxButtons::YesNo => MessageBoxResult::Yes,
MessageBoxButtons::YesNoCancel => MessageBoxResult::Yes,
};
ui.send_message(MessageBoxMessage::close(
self.handle,
MessageDirection::ToWidget,
result,
));
} else if message.destination() == *self.cancel {
ui.send_message(MessageBoxMessage::close(
self.handle(),
MessageDirection::ToWidget,
MessageBoxResult::Cancel,
));
} else if message.destination() == *self.no {
ui.send_message(MessageBoxMessage::close(
self.handle(),
MessageDirection::ToWidget,
MessageBoxResult::No,
));
}
} else if let Some(msg) = message.data::<MessageBoxMessage>() {
match msg {
MessageBoxMessage::Open { title, text } => {
if let Some(title) = title {
ui.send_message(WindowMessage::title(
self.handle(),
MessageDirection::ToWidget,
WindowTitle::text(title.clone()),
));
}
if let Some(text) = text {
ui.send_message(TextMessage::text(
*self.text,
MessageDirection::ToWidget,
text.clone(),
));
}
ui.send_message(WindowMessage::open_modal(
self.handle(),
MessageDirection::ToWidget,
true,
true,
));
}
MessageBoxMessage::Close(_) => {
ui.send_message(WindowMessage::close(
self.handle(),
MessageDirection::ToWidget,
));
}
}
}
}
fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
self.window.preview_message(ui, message);
}
fn handle_os_event(
&mut self,
self_handle: Handle<UiNode>,
ui: &mut UserInterface,
event: &OsEvent,
) {
self.window.handle_os_event(self_handle, ui, event);
}
}
pub struct MessageBoxBuilder<'b> {
window_builder: WindowBuilder,
buttons: MessageBoxButtons,
text: &'b str,
}
impl<'b> MessageBoxBuilder<'b> {
pub fn new(window_builder: WindowBuilder) -> Self {
Self {
window_builder,
buttons: MessageBoxButtons::Ok,
text: "",
}
}
pub fn with_text(mut self, text: &'b str) -> Self {
self.text = text;
self
}
pub fn with_buttons(mut self, buttons: MessageBoxButtons) -> Self {
self.buttons = buttons;
self
}
pub fn build(mut self, ctx: &mut BuildContext) -> Handle<UiNode> {
let ok_yes;
let mut no = Default::default();
let mut cancel = Default::default();
let text;
let content = match self.buttons {
MessageBoxButtons::Ok => GridBuilder::new(
WidgetBuilder::new()
.with_child({
text = TextBuilder::new(
WidgetBuilder::new().with_margin(Thickness::uniform(4.0)),
)
.with_text(self.text)
.with_wrap(WrapMode::Word)
.build(ctx);
text
})
.with_child({
ok_yes = ButtonBuilder::new(
WidgetBuilder::new()
.with_margin(Thickness::uniform(1.0))
.with_width(80.0)
.on_row(1)
.with_horizontal_alignment(HorizontalAlignment::Center),
)
.with_text("OK")
.build(ctx);
ok_yes
})
.with_margin(Thickness::uniform(5.0)),
)
.add_row(Row::stretch())
.add_row(Row::strict(25.0))
.add_column(Column::stretch())
.build(ctx),
MessageBoxButtons::YesNo => GridBuilder::new(
WidgetBuilder::new()
.with_child({
text = TextBuilder::new(WidgetBuilder::new())
.with_text(self.text)
.with_wrap(WrapMode::Word)
.build(ctx);
text
})
.with_child(
StackPanelBuilder::new(
WidgetBuilder::new()
.with_horizontal_alignment(HorizontalAlignment::Right)
.on_row(1)
.with_child({
ok_yes = ButtonBuilder::new(
WidgetBuilder::new()
.with_width(80.0)
.with_margin(Thickness::uniform(1.0)),
)
.with_text("Yes")
.build(ctx);
ok_yes
})
.with_child({
no = ButtonBuilder::new(
WidgetBuilder::new()
.with_width(80.0)
.with_margin(Thickness::uniform(1.0)),
)
.with_text("No")
.build(ctx);
no
}),
)
.with_orientation(Orientation::Horizontal)
.build(ctx),
)
.with_margin(Thickness::uniform(5.0)),
)
.add_row(Row::stretch())
.add_row(Row::strict(25.0))
.add_column(Column::stretch())
.build(ctx),
MessageBoxButtons::YesNoCancel => GridBuilder::new(
WidgetBuilder::new()
.with_child({
text = TextBuilder::new(WidgetBuilder::new())
.with_text(self.text)
.with_wrap(WrapMode::Word)
.build(ctx);
text
})
.with_child(
StackPanelBuilder::new(
WidgetBuilder::new()
.with_horizontal_alignment(HorizontalAlignment::Right)
.on_row(1)
.with_child({
ok_yes = ButtonBuilder::new(
WidgetBuilder::new()
.with_width(80.0)
.with_margin(Thickness::uniform(1.0)),
)
.with_text("Yes")
.build(ctx);
ok_yes
})
.with_child({
no = ButtonBuilder::new(
WidgetBuilder::new()
.with_width(80.0)
.with_margin(Thickness::uniform(1.0)),
)
.with_text("No")
.build(ctx);
no
})
.with_child({
cancel = ButtonBuilder::new(
WidgetBuilder::new()
.with_width(80.0)
.with_margin(Thickness::uniform(1.0)),
)
.with_text("Cancel")
.build(ctx);
cancel
}),
)
.with_orientation(Orientation::Horizontal)
.build(ctx),
)
.with_margin(Thickness::uniform(5.0)),
)
.add_row(Row::stretch())
.add_row(Row::strict(25.0))
.add_column(Column::stretch())
.build(ctx),
};
if self.window_builder.widget_builder.min_size.is_none() {
self.window_builder.widget_builder.min_size = Some(Vector2::new(200.0, 100.0));
}
self.window_builder.widget_builder.handle_os_events = true;
let is_open = self.window_builder.open;
let message_box = MessageBox {
buttons: self.buttons.into(),
window: self.window_builder.with_content(content).build_window(ctx),
ok_yes: ok_yes.into(),
no: no.into(),
cancel: cancel.into(),
text: text.into(),
};
let handle = ctx.add_node(UiNode::new(message_box));
if is_open {
ctx.push_picking_restriction(RestrictionEntry { handle, stop: true });
}
handle
}
}
#[cfg(test)]
mod test {
use crate::navigation::NavigationLayerBuilder;
use crate::{test::test_widget_deletion, widget::WidgetBuilder};
#[test]
fn test_deletion() {
test_widget_deletion(|ctx| NavigationLayerBuilder::new(WidgetBuilder::new()).build(ctx));
}
}