use std::{
ops::{
DerefMut,
Deref,
},
rc::Rc,
};
use crate::{
brush::Brush,
border::BorderBuilder,
popup::{PopupBuilder, Placement},
message::{
UiMessageData,
WidgetMessage,
PopupMessage,
UiMessage,
OsEvent,
ButtonState,
MenuMessage,
MenuItemMessage,
},
stack_panel::StackPanelBuilder,
node::UINode,
Control,
Orientation,
widget::{Widget, WidgetBuilder},
core::{
pool::Handle,
math::vec2::Vec2,
color::Color,
},
VerticalAlignment,
HorizontalAlignment,
Thickness,
grid::{GridBuilder, Row, Column},
text::TextBuilder,
BuildContext,
UserInterface
};
pub struct Menu<M: 'static, C: 'static + Control<M, C>> {
widget: Widget<M, C>,
active: bool,
}
impl<M: 'static, C: 'static + Control<M, C>> Deref for Menu<M, C> {
type Target = Widget<M, C>;
fn deref(&self) -> &Self::Target {
&self.widget
}
}
impl<M: 'static, C: 'static + Control<M, C>> DerefMut for Menu<M, C> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.widget
}
}
impl<M: 'static, C: 'static + Control<M, C>> Clone for Menu<M, C> {
fn clone(&self) -> Self {
Self {
widget: self.widget.raw_copy(),
active: self.active,
}
}
}
impl<M: 'static, C: 'static + Control<M, C>> Control<M, C> for Menu<M, C> {
fn raw_copy(&self) -> UINode<M, C> {
UINode::Menu(self.clone())
}
fn handle_routed_message(&mut self, ui: &mut UserInterface<M, C>, message: &mut UiMessage<M, C>) {
self.widget.handle_routed_message(ui, message);
if let UiMessageData::Menu(msg) = &message.data {
match msg {
MenuMessage::Activate => {
if !self.active {
ui.push_picking_restriction(self.handle());
self.active = true;
}
}
MenuMessage::Deactivate => {
if self.active {
self.active = false;
ui.remove_picking_restriction(self.handle());
let mut stack = self.children().to_vec();
while let Some(handle) = stack.pop() {
let node = ui.node(handle);
if let UINode::MenuItem(item) = node {
ui.send_message(UiMessage {
handled: false,
data: UiMessageData::MenuItem(MenuItemMessage::Close),
destination: handle,
});
stack.push(item.popup);
}
stack.extend_from_slice(node.children());
}
}
}
}
}
}
fn handle_os_event(&mut self, _self_handle: Handle<UINode<M, C>>, ui: &mut UserInterface<M, C>, event: &OsEvent) {
if let OsEvent::MouseInput { state, .. } = event {
if *state == ButtonState::Pressed && self.active {
let pos = ui.cursor_position();
if !self.widget.screen_bounds().contains(pos.x, pos.y) {
let mut any_picked = false;
let mut stack = self.children().to_vec();
'depth_search: while let Some(handle) = stack.pop() {
let node = ui.node(handle);
if let UINode::MenuItem(item) = node {
if ui.node(item.popup).screen_bounds().contains(pos.x, pos.y) {
any_picked = true;
break 'depth_search;
}
stack.push(item.popup);
}
stack.extend_from_slice(node.children());
}
if !any_picked {
ui.send_message(UiMessage {
handled: false,
data: UiMessageData::Menu(MenuMessage::Deactivate),
destination: self.handle(),
});
}
}
}
}
}
}
#[derive(Copy, Clone, PartialOrd, PartialEq, Hash)]
enum MenuItemPlacement {
Bottom,
Right,
}
pub struct MenuItem<M: 'static, C: 'static + Control<M, C>> {
widget: Widget<M, C>,
items: Vec<Handle<UINode<M, C>>>,
popup: Handle<UINode<M, C>>,
back: Handle<UINode<M, C>>,
placement: MenuItemPlacement,
}
impl<M: 'static, C: 'static + Control<M, C>> Deref for MenuItem<M, C> {
type Target = Widget<M, C>;
fn deref(&self) -> &Self::Target {
&self.widget
}
}
impl<M: 'static, C: 'static + Control<M, C>> DerefMut for MenuItem<M, C> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.widget
}
}
impl<M: 'static, C: 'static + Control<M, C>> Clone for MenuItem<M, C> {
fn clone(&self) -> Self {
Self {
widget: self.widget.raw_copy(),
items: self.items.clone(),
popup: self.popup,
back: self.back,
placement: self.placement,
}
}
}
fn find_menu<M: 'static, C: 'static + Control<M, C>>(from: Handle<UINode<M, C>>, ui: &UserInterface<M, C>) -> Handle<UINode<M, C>> {
let mut handle = from;
loop {
let popup = ui.find_by_criteria_up(handle, |n| {
if let UINode::Popup(_) = n { true } else { false }
});
if popup.is_none() {
return ui.find_by_criteria_up(handle, |n| {
if let UINode::Menu(_) = n { true } else { false }
});
} else {
if let UINode::Popup(popup) = ui.node(popup) {
handle = *popup.user_data_ref::<Handle<UINode<M, C>>>();
} else {
unreachable!();
}
}
}
}
impl<M: 'static, C: 'static + Control<M, C>> Control<M, C> for MenuItem<M, C> {
fn raw_copy(&self) -> UINode<M, C> {
UINode::MenuItem(self.clone())
}
fn handle_routed_message(&mut self, ui: &mut UserInterface<M, C>, message: &mut UiMessage<M, C>) {
self.widget.handle_routed_message(ui, message);
match &message.data {
UiMessageData::Widget(msg) => {
match msg {
WidgetMessage::MouseDown { .. } => {
let menu = find_menu(self.parent(), ui);
if menu.is_some() {
ui.send_message(UiMessage {
handled: false,
data: UiMessageData::Menu(MenuMessage::Activate),
destination: menu,
});
ui.send_message(UiMessage {
handled: false,
data: UiMessageData::MenuItem(MenuItemMessage::Open),
destination: self.handle(),
});
}
}
WidgetMessage::MouseUp { .. } => {
if !message.handled {
ui.send_message(UiMessage {
handled: false,
data: UiMessageData::MenuItem(MenuItemMessage::Click),
destination: self.handle(),
});
if self.items.is_empty() {
let menu = find_menu(self.parent(), ui);
if menu.is_some() {
ui.send_message(UiMessage {
handled: false,
data: UiMessageData::Menu(MenuMessage::Deactivate),
destination: menu,
});
}
}
message.handled = true;
}
}
WidgetMessage::MouseLeave => {
ui.send_message(WidgetMessage::background(self.back, Brush::Solid(Color::opaque(50, 50, 50))));
}
WidgetMessage::MouseEnter => {
ui.send_message(WidgetMessage::background(self.back, Brush::Solid(Color::opaque(130, 130, 130))));
let menu = find_menu(self.parent(), ui);
if menu.is_some() {
if let UINode::Menu(menu) = ui.node(menu) {
if menu.active {
ui.send_message(UiMessage {
handled: false,
data: UiMessageData::MenuItem(MenuItemMessage::Open),
destination: self.handle(),
});
}
}
}
}
_ => {}
}
}
UiMessageData::MenuItem(msg) => {
match msg {
MenuItemMessage::Open => {
if !self.items.is_empty() {
let position = match self.placement {
MenuItemPlacement::Bottom => {
self.screen_position + Vec2::new(0.0, self.actual_size().y)
}
MenuItemPlacement::Right => {
self.screen_position + Vec2::new(self.actual_size().x, 0.0)
}
};
ui.send_message(UiMessage {
handled: false,
data: UiMessageData::Popup(PopupMessage::Placement(Placement::Position(position))),
destination: self.popup,
});
ui.send_message(UiMessage {
handled: false,
data: UiMessageData::Popup(PopupMessage::Open),
destination: self.popup,
});
}
}
MenuItemMessage::Close => {
ui.send_message(UiMessage {
handled: false,
data: UiMessageData::Popup(PopupMessage::Close),
destination: self.popup,
});
}
MenuItemMessage::Click => {}
}
}
_ => {}
}
}
fn preview_message(&mut self, ui: &mut UserInterface<M, C>, message: &mut UiMessage<M, C>) {
if message.destination != self.handle() {
if let UiMessageData::MenuItem(msg) = &message.data {
if let MenuItemMessage::Open = msg {
let mut found = false;
let mut handle = message.destination;
while handle.is_some() {
if handle == self.handle() {
found = true;
break;
} else {
let node = ui.node(handle);
if let UINode::Popup(popup) = node {
handle = *popup.user_data_ref::<Handle<UINode<M, C>>>();
} else {
handle = node.parent();
}
}
}
if !found {
ui.send_message(UiMessage {
handled: false,
data: UiMessageData::MenuItem(MenuItemMessage::Close),
destination: self.handle(),
});
}
}
}
}
}
}
pub struct MenuBuilder<M: 'static, C: 'static + Control<M, C>> {
widget_builder: WidgetBuilder<M, C>,
items: Vec<Handle<UINode<M, C>>>,
}
impl<M: 'static, C: 'static + Control<M, C>> MenuBuilder<M, C> {
pub fn new(widget_builder: WidgetBuilder<M, C>) -> Self {
Self {
widget_builder,
items: Default::default(),
}
}
pub fn with_items(mut self, items: Vec<Handle<UINode<M, C>>>) -> Self {
self.items = items;
self
}
pub fn build(self, ctx: &mut BuildContext<M, C>) -> Handle<UINode<M, C>> {
for &item in self.items.iter() {
if let UINode::MenuItem(item) = &mut ctx[item] {
item.placement = MenuItemPlacement::Bottom;
}
}
let back = BorderBuilder::new(WidgetBuilder::new()
.with_child(StackPanelBuilder::new(WidgetBuilder::new()
.with_children(&self.items))
.with_orientation(Orientation::Horizontal)
.build(ctx)))
.build(ctx);
let menu = Menu {
widget: self.widget_builder
.with_child(back)
.build(),
active: false,
};
ctx.add_node(UINode::Menu(menu))
}
}
pub enum MenuItemContent<'a, 'b, M: 'static, C: 'static + Control<M, C>> {
None,
Text {
text: &'a str,
shortcut: &'b str,
icon: Handle<UINode<M, C>>,
},
Node(Handle<UINode<M, C>>),
}
impl<'a, 'b, M: 'static, C: 'static + Control<M, C>> MenuItemContent<'a, 'b, M, C> {
pub fn text_with_shortcut(text: &'a str, shortcut: &'b str) -> Self {
MenuItemContent::Text {
text,
shortcut,
icon: Default::default(),
}
}
pub fn text(text: &'a str) -> Self {
MenuItemContent::Text {
text,
shortcut: "",
icon: Default::default(),
}
}
}
pub struct MenuItemBuilder<'a, 'b, M: 'static, C: 'static + Control<M, C>> {
widget_builder: WidgetBuilder<M, C>,
items: Vec<Handle<UINode<M, C>>>,
content: MenuItemContent<'a, 'b, M, C>,
}
impl<'a, 'b, M: 'static, C: 'static + Control<M, C>> MenuItemBuilder<'a, 'b, M, C> {
pub fn new(widget_builder: WidgetBuilder<M, C>) -> Self {
Self {
widget_builder,
items: Default::default(),
content: MenuItemContent::None,
}
}
pub fn with_content(mut self, content: MenuItemContent<'a, 'b, M, C>) -> Self {
self.content = content;
self
}
pub fn with_items(mut self, items: Vec<Handle<UINode<M, C>>>) -> Self {
self.items = items;
self
}
pub fn build(self, ctx: &mut BuildContext<M, C>) -> Handle<UINode<M, C>> {
let content = match self.content {
MenuItemContent::None => Handle::NONE,
MenuItemContent::Text { text, shortcut, icon } => {
GridBuilder::new(WidgetBuilder::new()
.with_child(icon)
.with_child(TextBuilder::new(WidgetBuilder::new()
.with_vertical_alignment(VerticalAlignment::Center)
.with_margin(Thickness::uniform(1.0))
.on_column(1))
.with_text(text)
.build(ctx))
.with_child(TextBuilder::new(WidgetBuilder::new()
.with_vertical_alignment(VerticalAlignment::Center)
.with_horizontal_alignment(HorizontalAlignment::Right)
.with_margin(Thickness::uniform(1.0))
.on_column(2))
.with_text(shortcut)
.build(ctx)))
.add_row(Row::stretch())
.add_column(Column::auto())
.add_column(Column::stretch())
.add_column(Column::auto())
.build(ctx)
}
MenuItemContent::Node(node) => node,
};
let back = BorderBuilder::new(WidgetBuilder::new()
.with_child(content))
.build(ctx);
let popup = PopupBuilder::new(WidgetBuilder::new()
.with_min_size(Vec2::new(10.0, 10.0)))
.with_content(StackPanelBuilder::new(WidgetBuilder::new()
.with_children(&self.items))
.build(ctx))
.stays_open(true)
.build(ctx);
let menu = MenuItem {
widget: self.widget_builder
.with_child(back)
.build(),
popup,
items: self.items,
back,
placement: MenuItemPlacement::Right,
};
let handle = ctx.add_node(UINode::MenuItem(menu));
if let UINode::Popup(popup) = &mut ctx[popup] {
popup.user_data = Some(Rc::new(handle));
}
handle
}
}