From 76253abcc9d9977663d011b2f24ae9d984a6ce9b Mon Sep 17 00:00:00 2001 From: Paris DOUADY Date: Wed, 14 Feb 2024 17:07:23 +0100 Subject: [PATCH] migrate chat to newgui --- assets_gui/src/yakui_gui.rs | 4 +- goryak/src/scroll.rs | 74 +++++++++-- goryak/src/text.rs | 26 +++- native_app/src/gui/chat.rs | 123 ------------------ native_app/src/gui/hud.rs | 3 - native_app/src/gui/mod.rs | 1 - native_app/src/init.rs | 2 +- native_app/src/newgui/hud.rs | 2 + native_app/src/newgui/hud/chat.rs | 117 +++++++++++++++++ native_app/src/newgui/hud/windows/economy.rs | 6 +- native_app/src/newgui/hud/windows/network.rs | 28 +--- native_app/src/newgui/hud/windows/settings.rs | 4 +- 12 files changed, 218 insertions(+), 172 deletions(-) delete mode 100644 native_app/src/gui/chat.rs create mode 100644 native_app/src/newgui/hud/chat.rs diff --git a/assets_gui/src/yakui_gui.rs b/assets_gui/src/yakui_gui.rs index 1e8b7380..4d45124a 100644 --- a/assets_gui/src/yakui_gui.rs +++ b/assets_gui/src/yakui_gui.rs @@ -12,7 +12,7 @@ use goryak::{ background, button_primary, checkbox_value, constrained_viewport, dragvalue, icon, interact_box_radius, is_hovered, on_secondary_container, on_surface, outline_variant, round_rect, secondary_container, set_theme, surface, surface_variant, textc, use_changed, - RoundRect, Theme, VertScroll, + RoundRect, Theme, VertScrollSize, }; use prototypes::{prototypes_iter, GoodsCompanyID, GoodsCompanyPrototype}; @@ -80,7 +80,7 @@ impl State { } }); }); - VertScroll::Percent(1.0).show(|| { + VertScrollSize::Percent(1.0).show(|| { let mut l = List::column(); l.cross_axis_alignment = CrossAxisAlignment::Stretch; l.main_axis_size = MainAxisSize::Min; diff --git a/goryak/src/scroll.rs b/goryak/src/scroll.rs index 1464ee4e..52427846 100644 --- a/goryak/src/scroll.rs +++ b/goryak/src/scroll.rs @@ -8,12 +8,35 @@ use yakui_core::Response; use yakui_widgets::shapes::RoundedRectangle; #[derive(Debug)] -pub enum VertScroll { +pub enum VertScrollSize { + /// The scroll size is a percentage of the parent's size. Percent(f32), + /// The scroll size is exactly what is given (within constraints). + Exact(f32), + /// The scroll size is at most what is given (within constraints). Fixed(f32), + /// The scroll size is equal to the parent's size. Max, } +impl VertScrollSize { + pub fn show(self, children: F) -> Response { + yakui_widgets::util::widget_children::( + children, + VertScroll { + size: self, + align_bot: false, + }, + ) + } +} + +#[derive(Debug)] +pub struct VertScroll { + pub size: VertScrollSize, + pub align_bot: bool, +} + impl VertScroll { pub fn show(self, children: F) -> Response { yakui_widgets::util::widget_children::(children, self) @@ -40,7 +63,10 @@ impl Widget for VertScrollWidget { fn new() -> Self { Self { - props: VertScroll::Max, + props: VertScroll { + size: VertScrollSize::Max, + align_bot: false, + }, scroll_position: Cell::new(0.0), size: Cell::new(0.0), canvas_size: Cell::new(0.0), @@ -55,9 +81,10 @@ impl Widget for VertScrollWidget { } fn flex(&self) -> (u32, FlexFit) { - match self.props { - VertScroll::Max => (1, FlexFit::Tight), - VertScroll::Percent(_) => (1, FlexFit::Loose), + match self.props.size { + VertScrollSize::Max => (1, FlexFit::Tight), + VertScrollSize::Percent(_) => (1, FlexFit::Loose), + VertScrollSize::Exact(_) => (0, FlexFit::Tight), _ => (0, FlexFit::Loose), } } @@ -68,13 +95,17 @@ impl Widget for VertScrollWidget { let node = ctx.dom.get_current(); let mut canvas_size = Vec2::ZERO; - let main_axis_size = match self.props { - VertScroll::Max => constraints.max.y, - VertScroll::Fixed(h) => { + let main_axis_size = match self.props.size { + VertScrollSize::Max => constraints.max.y, + VertScrollSize::Fixed(h) => { constraints.max.y = constraints.max.y.min(h); constraints.min.y } - VertScroll::Percent(percent) => { + VertScrollSize::Exact(h) => { + constraints.max.y = constraints.max.y.min(h); + constraints.min.y + } + VertScrollSize::Percent(percent) => { constraints.max.y = constraints.max.y * percent; constraints.min.y } @@ -92,7 +123,10 @@ impl Widget for VertScrollWidget { canvas_size = canvas_size.max(child_size); } - let size = constraints.constrain(canvas_size); + let mut size = constraints.constrain(canvas_size); + if let VertScrollSize::Exact(_) = self.props.size { + size.y = size.y.max(constraints.max.y); + } self.canvas_size.set(canvas_size.y); self.size.set(size.y); @@ -103,7 +137,12 @@ impl Widget for VertScrollWidget { self.scroll_position.set(scroll_position); for &child in &node.children { - ctx.layout.set_pos(child, Vec2::new(0.0, -scroll_position)); + let mut off = Vec2::new(0.0, -scroll_position); + if self.props.align_bot { + off.y = size.y - canvas_size.y + scroll_position; + } + + ctx.layout.set_pos(child, off); } size @@ -134,10 +173,16 @@ impl Widget for VertScrollWidget { drawn_rect.size().y * (drawn_rect.size().y / self.canvas_size.get()); let remaining_space = drawn_rect.size().y - scroll_bar_height - SCROLLBAR_PAD_Y; + let mut pos_y = remaining_space * scrollbar_progress + SCROLLBAR_PAD_Y * 0.5; + + if self.props.align_bot { + pos_y = drawn_rect.size().y - scroll_bar_height - pos_y; + } + let scroll_bar_pos = drawn_rect.pos() + Vec2::new( drawn_rect.size().x - scrollbar_width * 0.5 - SCROLLBAR_PAD_X, - remaining_space * scrollbar_progress + SCROLLBAR_PAD_Y * 0.5, + pos_y, ); let scroll_bar_rect = Rect::from_pos_size( scroll_bar_pos, @@ -166,7 +211,10 @@ impl Widget for VertScrollWidget { fn event(&mut self, _ctx: EventContext<'_>, event: &WidgetEvent) -> EventResponse { match *event { - WidgetEvent::MouseScroll { delta } => { + WidgetEvent::MouseScroll { mut delta } => { + if self.props.align_bot { + delta.y = -delta.y; + } *self.scroll_position.get_mut() += delta.y; EventResponse::Sink } diff --git a/goryak/src/text.rs b/goryak/src/text.rs index 36dbcab3..01997821 100644 --- a/goryak/src/text.rs +++ b/goryak/src/text.rs @@ -1,9 +1,10 @@ use crate::DEFAULT_FONT_SIZE; use std::borrow::Cow; -use yakui_core::geometry::Color; +use yakui_core::geometry::{Color, Constraints, Vec2}; use yakui_core::Response; +use yakui_widgets::constrained; use yakui_widgets::font::FontName; -use yakui_widgets::widgets::{Text, TextResponse}; +use yakui_widgets::widgets::{Text, TextBox, TextResponse}; pub fn text>>(text: S) -> Response { Text::new(DEFAULT_FONT_SIZE, text.into()).show() @@ -15,3 +16,24 @@ pub fn monospace>>(col: Color, text: S) -> Response bool { + let mut activated = false; + constrained( + Constraints { + min: Vec2::new(width, 20.0), + max: Vec2::new(f32::INFINITY, f32::INFINITY), + }, + || { + let mut text = TextBox::new(x.clone()); + text.placeholder = placeholder.to_string(); + text.fill = Some(Color::rgba(0, 0, 0, 50)); + let resp = text.show().into_inner(); + if let Some(changed) = resp.text { + *x = changed; + } + activated = resp.activated; + }, + ); + activated +} diff --git a/native_app/src/gui/chat.rs b/native_app/src/gui/chat.rs deleted file mode 100644 index aa333ac3..00000000 --- a/native_app/src/gui/chat.rs +++ /dev/null @@ -1,123 +0,0 @@ -use egui::panel::TopBottomSide; -use egui::{Align2, Color32, Frame, RichText, ScrollArea, TextBuffer, TopBottomPanel}; - -use geom::Color; -use prototypes::{GameDuration, GameTime}; -use simulation::multiplayer::chat::{Message, MessageKind}; -use simulation::multiplayer::MultiplayerState; -use simulation::world_command::WorldCommand; -use simulation::Simulation; - -use crate::inputmap::{InputAction, InputMap}; -use crate::uiworld::UiWorld; - -#[derive(Default)] -pub struct GUIChatState { - cur_msg: String, - chat_bar_showed: bool, -} - -pub fn chat(ui: &egui::Context, uiw: &UiWorld, sim: &Simulation) { - const MAX_MESSAGES: usize = 30; - let mut state = uiw.write::(); - let five_minute_ago = sim.read::().instant() - GameDuration::from_minutes(5); - - let mstate = sim.read::(); - - let just_opened = uiw - .read::() - .just_act - .contains(&InputAction::OpenChat); - - if just_opened { - state.chat_bar_showed = true; - } - - let msgs: Vec<_> = mstate - .chat - .messages_since(five_minute_ago) - .take(MAX_MESSAGES) - .collect(); - - if !state.chat_bar_showed && msgs.is_empty() { - return; - } - - egui::Window::new("Chat") - .title_bar(false) - .fixed_size(egui::Vec2::new(250.0, 300.0)) - .frame(Frame::default().fill(if state.chat_bar_showed { - Color32::from_black_alpha(192) - } else { - Color32::from_black_alpha(64) - })) - .anchor(Align2::LEFT_BOTTOM, (0.0, -55.0)) - .show(ui, |ui| { - ScrollArea::vertical().stick_to_bottom(true).show(ui, |ui| { - ui.allocate_space(egui::Vec2::new(250.0, 0.0)); - - if msgs.len() < 12 { - ui.add_space((12 - msgs.len()) as f32 * 24.0); - } - - for message in msgs.iter().rev() { - let color = message.color; - - let text = RichText::new(message.text.clone()); - - ui.horizontal_wrapped(|ui| { - ui.add_space(5.0); - ui.colored_label( - Color32::from_rgb( - (color.r * 255.0) as u8, - (color.g * 255.0) as u8, - (color.b * 255.0) as u8, - ), - text, - ); - }); - ui.add_space(2.0); - } - }); - - TopBottomPanel::new(TopBottomSide::Bottom, "chat_bar") - .frame(Frame::default()) - .show_separator_line(false) - .show_inside(ui, |ui| { - if state.chat_bar_showed { - let response = ui.add( - egui::TextEdit::singleline(&mut state.cur_msg) - .desired_width(250.0) - .margin(egui::Vec2::new(8.0, 6.0)), - ); - - if just_opened { - response.request_focus(); - } - - if response.lost_focus() { - let msg = state.cur_msg.take(); - - if !msg.is_empty() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { - // let rng = common::rand::randu64(common::hash_u64(msg.as_bytes())); - // let color = Color::hsv(rng * 360.0, 0.8, 1.0, 1.0); - - uiw.commands().push(WorldCommand::SendMessage { - message: Message { - name: "player".to_string(), - text: msg, - sent_at: sim.read::().instant(), - color: Color::WHITE, - kind: MessageKind::PlayerChat, - }, - }) - } - - state.chat_bar_showed = false; - } - } else { - ui.allocate_space(egui::Vec2::new(240.0, 26.0)); - } - }); - }); -} diff --git a/native_app/src/gui/hud.rs b/native_app/src/gui/hud.rs index 1c8dc636..d4c37be7 100644 --- a/native_app/src/gui/hud.rs +++ b/native_app/src/gui/hud.rs @@ -4,7 +4,6 @@ use prototypes::Money; use simulation::economy::Government; use simulation::Simulation; -use crate::gui::chat::chat; use crate::gui::debug_inspect::debug_inspector; use crate::gui::debug_window::debug_window; use crate::newgui::{ErrorTooltip, GuiState, PotentialCommands}; @@ -19,8 +18,6 @@ pub fn render_oldgui(ui: &Context, uiworld: &UiWorld, sim: &Simulation) { debug_inspector(ui, uiworld, sim); - chat(ui, uiworld, sim); - debug_window(ui, uiworld, sim); tooltip(ui, uiworld, sim); diff --git a/native_app/src/gui/mod.rs b/native_app/src/gui/mod.rs index d69875ca..9910c373 100644 --- a/native_app/src/gui/mod.rs +++ b/native_app/src/gui/mod.rs @@ -1,4 +1,3 @@ -pub mod chat; pub mod debug_inspect; pub mod debug_window; pub mod hud; diff --git a/native_app/src/init.rs b/native_app/src/init.rs index 255a0f81..23d8421c 100644 --- a/native_app/src/init.rs +++ b/native_app/src/init.rs @@ -1,9 +1,9 @@ use crate::game_loop::Timings; -use crate::gui::chat::GUIChatState; use crate::gui::debug_window::{DebugObjs, DebugState, TestFieldProperties}; use crate::inputmap::{Bindings, InputMap}; use crate::network::NetworkState; use crate::newgui::bulldozer::BulldozerState; +use crate::newgui::chat::GUIChatState; use crate::newgui::follow::FollowEntity; use crate::newgui::lotbrush::LotBrushResource; use crate::newgui::roadbuild::RoadBuildResource; diff --git a/native_app/src/newgui/hud.rs b/native_app/src/newgui/hud.rs index aaf388da..6bc2971e 100644 --- a/native_app/src/newgui/hud.rs +++ b/native_app/src/newgui/hud.rs @@ -17,6 +17,7 @@ use crate::newgui::windows::settings::Settings; use crate::newgui::GuiState; use crate::uiworld::{SaveLoadState, UiWorld}; +pub mod chat; mod menu; mod time_controls; pub mod toolbox; @@ -35,6 +36,7 @@ pub fn render_newgui(uiworld: &UiWorld, sim: &Simulation) { power_errors(uiworld, sim); new_toolbox(uiworld, sim); menu_bar(uiworld, sim); + chat::chat(uiworld, sim); new_inspector(uiworld, sim); uiworld.write::().windows.render(uiworld, sim); time_controls(uiworld, sim); diff --git a/native_app/src/newgui/hud/chat.rs b/native_app/src/newgui/hud/chat.rs new file mode 100644 index 00000000..26cacdaf --- /dev/null +++ b/native_app/src/newgui/hud/chat.rs @@ -0,0 +1,117 @@ +use egui::TextBuffer; +use yakui::{constrained, reflow, Alignment, Color, Constraints, Dim2, Vec2}; + +use goryak::{ + blur_bg, fixed_spacer, mincolumn, padxy, pivot, secondary_container, text_edit, textc, + VertScroll, VertScrollSize, +}; +use prototypes::{GameDuration, GameTime}; +use simulation::multiplayer::chat::{Message, MessageKind}; +use simulation::multiplayer::MultiplayerState; +use simulation::world_command::WorldCommand; +use simulation::Simulation; + +use crate::inputmap::{InputAction, InputMap}; +use crate::uiworld::UiWorld; + +#[derive(Default)] +pub struct GUIChatState { + cur_msg: String, + chat_bar_showed: bool, +} + +pub fn chat(uiw: &UiWorld, sim: &Simulation) { + const MAX_MESSAGES: usize = 30; + let mut state = uiw.write::(); + let five_minute_ago = sim.read::().instant() - GameDuration::from_minutes(5); + + let mstate = sim.read::(); + + let just_opened = uiw + .read::() + .just_act + .contains(&InputAction::OpenChat); + + if just_opened { + state.chat_bar_showed = true; + } + + if uiw + .read::() + .just_act + .contains(&InputAction::Close) + { + state.chat_bar_showed = false; + state.cur_msg.clear(); + } + + let msgs: Vec<_> = mstate + .chat + .messages_since(five_minute_ago) + .take(MAX_MESSAGES) + .collect(); + + if !state.chat_bar_showed && msgs.is_empty() { + return; + } + + reflow(Alignment::BOTTOM_LEFT, Dim2::pixels(0.0, -192.0), || { + pivot(Alignment::BOTTOM_LEFT, || { + let alpha = if state.chat_bar_showed { 0.7 } else { 0.2 }; + blur_bg(secondary_container().with_alpha(alpha), 0.0, || { + mincolumn(0.0, || { + VertScroll { + size: VertScrollSize::Exact(300.0), + align_bot: true, + } + .show(|| { + constrained( + Constraints { + min: Vec2::new(250.0, 0.0), + max: Vec2::new(250.0, f32::INFINITY), + }, + || { + padxy(8.0, 8.0, || { + mincolumn(8.0, || { + for message in msgs.iter().rev() { + let color = message.color; + + let text = message.text.clone(); + + textc( + Color::rgb( + (color.r * 255.0) as u8, + (color.g * 255.0) as u8, + (color.b * 255.0) as u8, + ), + text, + ); + } + }); + }); + }, + ); + }); + if state.chat_bar_showed { + if text_edit(250.0, &mut state.cur_msg, "") { + if !state.cur_msg.is_empty() { + uiw.commands().push(WorldCommand::SendMessage { + message: Message { + name: "player".to_string(), + text: state.cur_msg.take(), + sent_at: sim.read::().instant(), + color: geom::Color::WHITE, + kind: MessageKind::PlayerChat, + }, + }); + state.chat_bar_showed = false; + } + } + } else { + fixed_spacer((0.0, 30.0)); + } + }); + }); + }); + }); +} diff --git a/native_app/src/newgui/hud/windows/economy.rs b/native_app/src/newgui/hud/windows/economy.rs index b0180da9..4046608f 100644 --- a/native_app/src/newgui/hud/windows/economy.rs +++ b/native_app/src/newgui/hud/windows/economy.rs @@ -11,7 +11,7 @@ use engine::Tesselator; use geom::AABB; use goryak::{ constrained_viewport, mincolumn, minrow, on_primary_container, padxy, pady, - selectable_label_primary, sized_canvas, textc, VertScroll, Window, + selectable_label_primary, sized_canvas, textc, VertScrollSize, Window, }; use prototypes::{ItemID, DELTA_F64}; use simulation::economy::{ @@ -235,7 +235,7 @@ pub fn economy(uiw: &UiWorld, sim: &Simulation, opened: &mut bool) { }); }); - VertScroll::Fixed(300.0).show(|| { + VertScrollSize::Fixed(300.0).show(|| { constrained(Constraints::loose(Vec2::new(300.0, 1000000.0)), || { let mut overall_total = 0; let mut g = CountGrid::col(2); @@ -331,7 +331,7 @@ pub fn economy(uiw: &UiWorld, sim: &Simulation, opened: &mut bool) { fn render_market_prices(sim: &Simulation) { let market = sim.read::(); - VertScroll::Fixed(300.0).show(|| { + VertScrollSize::Fixed(300.0).show(|| { let mut grid = CountGrid::col(2); grid.main_axis_size = MainAxisSize::Min; grid.show(|| { diff --git a/native_app/src/newgui/hud/windows/network.rs b/native_app/src/newgui/hud/windows/network.rs index dc6f1ee2..6e371d57 100644 --- a/native_app/src/newgui/hud/windows/network.rs +++ b/native_app/src/newgui/hud/windows/network.rs @@ -2,12 +2,13 @@ use std::borrow::Cow; use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; -use yakui::widgets::{Pad, TextBox}; -use yakui::{constrained, divider, Color, Constraints, Vec2}; +use yakui::divider; +use yakui::widgets::Pad; use common::saveload::Encoder; use goryak::{ - button_primary, checkbox_value, error, on_secondary_container, outline, textc, Window, + button_primary, checkbox_value, error, on_secondary_container, outline, text_edit, textc, + Window, }; use simulation::Simulation; @@ -32,23 +33,6 @@ fn label(x: impl Into>) { textc(on_secondary_container(), x); } -fn text_edit(x: &mut String, placeholder: &str) { - constrained( - Constraints { - min: Vec2::new(100.0, 20.0), - max: Vec2::new(f32::INFINITY, f32::INFINITY), - }, - || { - let mut text = TextBox::new(x.clone()); - text.placeholder = placeholder.to_string(); - text.fill = Some(Color::rgba(0, 0, 0, 50)); - if let Some(changed) = text.show().into_inner().text { - *x = changed; - } - }, - ); -} - /// Network window /// Allows to connect to a server or start a server pub fn network(uiworld: &UiWorld, sim: &Simulation, opened: &mut bool) { @@ -71,7 +55,7 @@ pub fn network(uiworld: &UiWorld, sim: &Simulation, opened: &mut bool) { divider(outline(), 5.0, 1.0); } - text_edit(&mut info.name, "Name"); + text_edit(200.0, &mut info.name, "Name"); if info.name.is_empty() { label("please enter your name"); @@ -86,7 +70,7 @@ pub fn network(uiworld: &UiWorld, sim: &Simulation, opened: &mut bool) { divider(outline(), 5.0, 1.0); - text_edit(&mut info.ip, "IP"); + text_edit(200.0, &mut info.ip, "IP"); if button_primary("Connect").show().clicked { if let Some(c) = crate::network::start_client(&mut info) { diff --git a/native_app/src/newgui/hud/windows/settings.rs b/native_app/src/newgui/hud/windows/settings.rs index 0b376cfe..5fdf9336 100644 --- a/native_app/src/newgui/hud/windows/settings.rs +++ b/native_app/src/newgui/hud/windows/settings.rs @@ -10,7 +10,7 @@ use engine::GfxSettings; use engine::ShadowQuality; use goryak::{ button_primary, checkbox_value, combo_box, dragvalue, icon_button, minrow, - on_secondary_container, outline, padx, padxy, textc, VertScroll, Window, + on_secondary_container, outline, padx, padxy, textc, VertScrollSize, Window, }; use simulation::Simulation; @@ -129,7 +129,7 @@ pub fn settings(uiw: &UiWorld, _: &Simulation, opened: &mut bool) { .show(|| { profiling::scope!("gui::window::settings"); - VertScroll::Percent(0.8).show(|| { + VertScrollSize::Percent(0.8).show(|| { let mut l = List::column(); l.item_spacing = 5.0; l.main_axis_size = MainAxisSize::Min;