fyroxed_base 0.16.0

A scene editor for Fyrox game engine
Documentation
use crate::{gui::make_dropdown_list_option, Brush, Color, DropdownListBuilder, GameEngine};
use fyrox::{
    core::{pool::Handle, scope_profile},
    gui::{
        border::BorderBuilder,
        button::{ButtonBuilder, ButtonMessage},
        copypasta::ClipboardProvider,
        dropdown_list::DropdownListMessage,
        formatted_text::WrapMode,
        grid::{Column, GridBuilder, Row},
        list_view::{ListView, ListViewBuilder, ListViewMessage},
        menu::{MenuItemBuilder, MenuItemContent, MenuItemMessage},
        message::{MessageDirection, UiMessage},
        popup::{Placement, PopupBuilder, PopupMessage},
        scroll_viewer::ScrollViewerBuilder,
        stack_panel::StackPanelBuilder,
        text::{Text, TextBuilder},
        widget::WidgetBuilder,
        window::{WindowBuilder, WindowTitle},
        BuildContext, HorizontalAlignment, Orientation, Thickness, UiNode,
    },
    utils::log::{LogMessage, MessageKind},
};
use std::sync::mpsc::Receiver;

struct ContextMenu {
    menu: Handle<UiNode>,
    copy: Handle<UiNode>,
    placement_target: Handle<UiNode>,
}

impl ContextMenu {
    pub fn new(ctx: &mut BuildContext) -> Self {
        let copy;
        let menu = PopupBuilder::new(WidgetBuilder::new())
            .with_content(
                StackPanelBuilder::new(WidgetBuilder::new().with_child({
                    copy = MenuItemBuilder::new(WidgetBuilder::new())
                        .with_content(MenuItemContent::text("Copy"))
                        .build(ctx);
                    copy
                }))
                .build(ctx),
            )
            .build(ctx);

        Self {
            menu,
            copy,
            placement_target: Default::default(),
        }
    }

    pub fn handle_ui_message(&mut self, message: &UiMessage, engine: &mut GameEngine) {
        if let Some(PopupMessage::Placement(Placement::Cursor(target))) = message.data() {
            if message.destination() == self.menu {
                self.placement_target = *target;
            }
        } else if let Some(MenuItemMessage::Click) = message.data() {
            if message.destination() == self.copy {
                if let Some(field) = engine
                    .user_interface
                    .try_get_node(self.placement_target)
                    .and_then(|n| n.query_component::<Text>())
                {
                    let text = field.text();
                    if let Some(mut clipboard) = engine.user_interface.clipboard_mut() {
                        let _ = clipboard.set_contents(text);
                    }
                }
            }
        }
    }
}

pub struct LogPanel {
    pub window: Handle<UiNode>,
    messages: Handle<UiNode>,
    clear: Handle<UiNode>,
    receiver: Receiver<LogMessage>,
    severity: MessageKind,
    severity_list: Handle<UiNode>,
    context_menu: ContextMenu,
}

impl LogPanel {
    pub fn new(ctx: &mut BuildContext, message_receiver: Receiver<LogMessage>) -> Self {
        let messages;
        let clear;
        let severity_list;
        let window = WindowBuilder::new(WidgetBuilder::new())
            .can_minimize(false)
            .with_title(WindowTitle::Text("Message Log".to_owned()))
            .with_content(
                GridBuilder::new(
                    WidgetBuilder::new()
                        .with_child(
                            StackPanelBuilder::new(
                                WidgetBuilder::new()
                                    .with_horizontal_alignment(HorizontalAlignment::Right)
                                    .on_row(0)
                                    .on_column(0)
                                    .with_child({
                                        clear = ButtonBuilder::new(
                                            WidgetBuilder::new()
                                                .with_width(120.0)
                                                .with_margin(Thickness::uniform(1.0)),
                                        )
                                        .with_text("Clear")
                                        .build(ctx);
                                        clear
                                    })
                                    .with_child({
                                        severity_list = DropdownListBuilder::new(
                                            WidgetBuilder::new()
                                                .with_width(120.0)
                                                .with_margin(Thickness::uniform(1.0)),
                                        )
                                        .with_items(vec![
                                            make_dropdown_list_option(ctx, "Info+"),
                                            make_dropdown_list_option(ctx, "Warnings+"),
                                            make_dropdown_list_option(ctx, "Errors"),
                                        ])
                                        // Warnings+
                                        .with_selected(1)
                                        .build(ctx);
                                        severity_list
                                    }),
                            )
                            .with_orientation(Orientation::Horizontal)
                            .build(ctx),
                        )
                        .with_child({
                            messages = ListViewBuilder::new(
                                WidgetBuilder::new()
                                    .with_margin(Thickness::uniform(1.0))
                                    .on_row(1)
                                    .on_column(0),
                            )
                            .with_scroll_viewer(
                                ScrollViewerBuilder::new(
                                    WidgetBuilder::new().with_margin(Thickness::uniform(3.0)),
                                )
                                .with_horizontal_scroll_allowed(true)
                                .with_vertical_scroll_allowed(true)
                                .build(ctx),
                            )
                            .build(ctx);
                            messages
                        }),
                )
                .add_row(Row::strict(26.0))
                .add_row(Row::stretch())
                .add_column(Column::stretch())
                .build(ctx),
            )
            .build(ctx);

        let context_menu = ContextMenu::new(ctx);

        Self {
            window,
            messages,
            clear,
            receiver: message_receiver,
            severity: MessageKind::Warning,
            severity_list,
            context_menu,
        }
    }

    pub fn handle_ui_message(&mut self, message: &UiMessage, engine: &mut GameEngine) {
        scope_profile!();

        if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
            if message.destination() == self.clear {
                engine.user_interface.send_message(ListViewMessage::items(
                    self.messages,
                    MessageDirection::ToWidget,
                    vec![],
                ));
            }
        } else if let Some(DropdownListMessage::SelectionChanged(Some(idx))) =
            message.data::<DropdownListMessage>()
        {
            if message.destination() == self.severity_list
                && message.direction() == MessageDirection::FromWidget
            {
                match idx {
                    0 => self.severity = MessageKind::Information,
                    1 => self.severity = MessageKind::Warning,
                    2 => self.severity = MessageKind::Error,
                    _ => (),
                };
            }
        }

        self.context_menu.handle_ui_message(message, engine);
    }

    pub fn update(&mut self, engine: &mut GameEngine) {
        let mut count = engine
            .user_interface
            .node(self.messages)
            .cast::<ListView>()
            .map(|v| v.items().len())
            .unwrap_or_default();

        let mut item_to_bring_into_view = Handle::NONE;

        while let Ok(msg) = self.receiver.try_recv() {
            if msg.kind < self.severity {
                continue;
            }

            let text = format!("[{:.2}s] {}", msg.time.as_secs_f32(), msg.content);

            let ctx = &mut engine.user_interface.build_ctx();
            let item = BorderBuilder::new(
                WidgetBuilder::new()
                    .with_background(Brush::Solid(if count % 2 == 0 {
                        Color::opaque(70, 70, 70)
                    } else {
                        Color::opaque(40, 40, 40)
                    }))
                    .with_child(
                        TextBuilder::new(
                            WidgetBuilder::new()
                                .with_context_menu(self.context_menu.menu)
                                .with_margin(Thickness::uniform(1.0))
                                .with_foreground(Brush::Solid(match msg.kind {
                                    MessageKind::Information => Color::opaque(210, 210, 210),
                                    MessageKind::Warning => Color::ORANGE,
                                    MessageKind::Error => Color::RED,
                                })),
                        )
                        .with_text(text)
                        .with_wrap(WrapMode::Word)
                        .build(ctx),
                    ),
            )
            .build(ctx);

            engine
                .user_interface
                .send_message(ListViewMessage::add_item(
                    self.messages,
                    MessageDirection::ToWidget,
                    item,
                ));

            item_to_bring_into_view = item;

            count += 1;
        }

        if item_to_bring_into_view.is_some() {
            engine
                .user_interface
                .send_message(ListViewMessage::bring_item_into_view(
                    self.messages,
                    MessageDirection::ToWidget,
                    item_to_bring_into_view,
                ));
        }
    }
}