#![warn(missing_docs)]
use crate::{
button::{ButtonBuilder, ButtonMessage},
control_trait_proxy_impls,
core::{
algebra::Vector2, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
visitor::prelude::*,
},
formatted_text::WrapMode,
grid::{Column, GridBuilder, Row},
message::UiMessage,
stack_panel::StackPanelBuilder,
text::{TextBuilder, TextMessage},
widget::{Widget, WidgetBuilder},
window::{Window, WindowBuilder, WindowMessage, WindowTitle},
BuildContext, Control, HorizontalAlignment, Orientation, RestrictionEntry, Thickness, UiNode,
UserInterface,
};
use crate::button::Button;
use crate::message::MessageData;
use crate::text::Text;
use crate::window::WindowAlignment;
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 MessageData for MessageBoxMessage {}
#[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)]
#[reflect(derived_type = "UiNode")]
pub struct MessageBox {
#[component(include)]
pub window: Window,
pub buttons: InheritableVariable<MessageBoxButtons>,
pub ok_yes: InheritableVariable<Handle<Button>>,
pub no: InheritableVariable<Handle<Button>>,
pub cancel: InheritableVariable<Handle<Button>>,
pub text: InheritableVariable<Handle<Text>>,
}
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())
.to_base()
.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 {
control_trait_proxy_impls!(window);
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(self.handle, MessageBoxMessage::Close(result));
} else if message.destination() == *self.cancel {
ui.send(
self.handle(),
MessageBoxMessage::Close(MessageBoxResult::Cancel),
);
} else if message.destination() == *self.no {
ui.send(
self.handle(),
MessageBoxMessage::Close(MessageBoxResult::No),
);
}
} else if let Some(msg) = message.data_for::<MessageBoxMessage>(self.handle) {
match msg {
MessageBoxMessage::Open { title, text } => {
if let Some(title) = title {
ui.send(
self.handle(),
WindowMessage::Title(WindowTitle::text(title.clone())),
);
}
if let Some(text) = text {
ui.send(*self.text, TextMessage::Text(text.clone()));
}
ui.send(
self.handle(),
WindowMessage::Open {
alignment: WindowAlignment::Center,
modal: true,
focus_content: true,
},
);
}
MessageBoxMessage::Close(_) => {
ui.send(self.handle(), WindowMessage::Close);
ui.try_send_response(message);
}
}
}
}
}
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<MessageBox> {
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(message_box);
if is_open {
ctx.push_picking_restriction(RestrictionEntry {
handle: handle.to_base(),
stop: true,
});
}
handle.to_variant()
}
}
#[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));
}
}