mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
326 lines
13 KiB
Rust
326 lines
13 KiB
Rust
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
|
|
use crate::fyrox::graph::BaseSceneGraph;
|
|
use crate::fyrox::{
|
|
core::{
|
|
log::{LogMessage, MessageKind},
|
|
pool::Handle,
|
|
},
|
|
gui::{
|
|
border::BorderBuilder,
|
|
button::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, RcUiNodeHandle, Thickness, UiNode,
|
|
},
|
|
};
|
|
use crate::settings::Settings;
|
|
use crate::{
|
|
gui::{make_dropdown_list_option, make_image_button_with_tooltip},
|
|
load_image, Brush, Color, DropdownListBuilder, Engine,
|
|
};
|
|
use fyrox::gui::menu::ContextMenuBuilder;
|
|
use std::sync::mpsc::Receiver;
|
|
|
|
struct ContextMenu {
|
|
menu: RcUiNodeHandle,
|
|
copy: Handle<UiNode>,
|
|
placement_target: Handle<UiNode>,
|
|
}
|
|
|
|
impl ContextMenu {
|
|
pub fn new(ctx: &mut BuildContext) -> Self {
|
|
let copy;
|
|
let menu = ContextMenuBuilder::new(
|
|
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);
|
|
let menu = RcUiNodeHandle::new(menu, ctx.sender());
|
|
|
|
Self {
|
|
menu,
|
|
copy,
|
|
placement_target: Default::default(),
|
|
}
|
|
}
|
|
|
|
pub fn handle_ui_message(&mut self, message: &UiMessage, engine: &mut Engine) {
|
|
if let Some(PopupMessage::Placement(Placement::Cursor(target))) = message.data() {
|
|
if message.destination() == self.menu.handle() {
|
|
self.placement_target = *target;
|
|
}
|
|
} else if let Some(MenuItemMessage::Click) = message.data() {
|
|
if message.destination() == self.copy {
|
|
if let Some(field) = engine
|
|
.user_interfaces
|
|
.first_mut()
|
|
.try_get(self.placement_target)
|
|
.and_then(|n| n.query_component::<Text>())
|
|
{
|
|
let text = field.text();
|
|
if let Some(mut clipboard) = engine.user_interfaces.first_mut().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().with_name("LogPanel"))
|
|
.can_minimize(false)
|
|
.with_title(WindowTitle::text("Message Log"))
|
|
.with_content(
|
|
GridBuilder::new(
|
|
WidgetBuilder::new()
|
|
.with_child(
|
|
StackPanelBuilder::new(
|
|
WidgetBuilder::new()
|
|
.with_horizontal_alignment(HorizontalAlignment::Left)
|
|
.on_row(0)
|
|
.on_column(0)
|
|
.with_child({
|
|
clear = make_image_button_with_tooltip(
|
|
ctx,
|
|
24.0,
|
|
24.0,
|
|
load_image!("../resources/clear.png"),
|
|
"Clear the log.",
|
|
Some(0),
|
|
);
|
|
clear
|
|
})
|
|
.with_child({
|
|
severity_list = DropdownListBuilder::new(
|
|
WidgetBuilder::new()
|
|
.with_tab_index(Some(1))
|
|
.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 Engine) {
|
|
if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
|
|
if message.destination() == self.clear {
|
|
engine
|
|
.user_interfaces
|
|
.first_mut()
|
|
.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, settings: &Settings, engine: &mut Engine) {
|
|
let ui = engine.user_interfaces.first();
|
|
|
|
let existing_items = ui
|
|
.node(self.messages)
|
|
.cast::<ListView>()
|
|
.map(|v| v.items())
|
|
.unwrap();
|
|
|
|
let mut count = existing_items.len();
|
|
|
|
if count > settings.general.max_log_entries {
|
|
let delta = count - settings.general.max_log_entries;
|
|
// Remove every item in the head of the list of entries to keep the amount of entries
|
|
// in the limits.
|
|
//
|
|
// TODO: This is suboptimal, because it creates a message per each excessive entry, which
|
|
// might be slow to process in case of large amount of messages.
|
|
for item in existing_items.iter().take(delta) {
|
|
ui.send_message(ListViewMessage::remove_item(
|
|
self.messages,
|
|
MessageDirection::ToWidget,
|
|
*item,
|
|
));
|
|
}
|
|
|
|
count -= delta;
|
|
}
|
|
|
|
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_interfaces.first_mut().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.clone())
|
|
.with_margin(Thickness::uniform(1.0))
|
|
.with_foreground(Brush::Solid(match msg.kind {
|
|
MessageKind::Information => Color::ANTIQUE_WHITE,
|
|
MessageKind::Warning => Color::GOLD,
|
|
MessageKind::Error => Color::RED,
|
|
})),
|
|
)
|
|
.with_text(text)
|
|
.with_wrap(WrapMode::Word)
|
|
.build(ctx),
|
|
),
|
|
)
|
|
.build(ctx);
|
|
|
|
engine
|
|
.user_interfaces
|
|
.first_mut()
|
|
.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_interfaces
|
|
.first_mut()
|
|
.send_message(ListViewMessage::bring_item_into_view(
|
|
self.messages,
|
|
MessageDirection::ToWidget,
|
|
item_to_bring_into_view,
|
|
));
|
|
}
|
|
}
|
|
}
|