diff --git a/assets_gui/src/yakui_gui.rs b/assets_gui/src/yakui_gui.rs index 9c15d01a..1e8b7380 100644 --- a/assets_gui/src/yakui_gui.rs +++ b/assets_gui/src/yakui_gui.rs @@ -429,9 +429,10 @@ pub fn resizebar_vert(off: &mut Response>, scrollbar_on_left_ let last_val = use_state(|| None); let mut hovered = false; let d = yakui::draggable(|| { - constrained(Constraints::tight(Vec2::new(5.0, f32::INFINITY)), || { - hovered = *is_hovered(); - }); + hovered = is_hovered(|| { + constrained(Constraints::tight(Vec2::new(5.0, f32::INFINITY)), || {}); + }) + .hovered; }) .dragging; let delta = d diff --git a/goryak/src/hovered.rs b/goryak/src/hovered.rs index 89604ec4..c388a952 100644 --- a/goryak/src/hovered.rs +++ b/goryak/src/hovered.rs @@ -1,27 +1,34 @@ use yakui_core::event::{EventInterest, EventResponse, WidgetEvent}; use yakui_core::widget::{EventContext, Widget}; use yakui_core::Response; -use yakui_widgets::util::widget; +use yakui_widgets::util::widget_children; -pub fn is_hovered() -> Response { - widget::(()) +pub fn is_hovered(children: impl FnOnce()) -> Response { + widget_children::(children, ()) +} + +#[derive(Debug, Copy, Clone, Default)] +pub struct IsHoveredResponse { + pub hovered: bool, } #[derive(Debug)] -pub struct IsHovered { - hovered: bool, +pub struct IsHoveredWidget { + resp: IsHoveredResponse, } -impl Widget for IsHovered { +impl Widget for IsHoveredWidget { type Props<'a> = (); - type Response = bool; + type Response = IsHoveredResponse; fn new() -> Self { - Self { hovered: false } + Self { + resp: Default::default(), + } } fn update(&mut self, _: Self::Props<'_>) -> Self::Response { - self.hovered + self.resp } fn event_interest(&self) -> EventInterest { @@ -30,8 +37,8 @@ impl Widget for IsHovered { fn event(&mut self, _: EventContext<'_>, event: &WidgetEvent) -> EventResponse { match *event { - WidgetEvent::MouseEnter => self.hovered = true, - WidgetEvent::MouseLeave => self.hovered = false, + WidgetEvent::MouseEnter => self.resp.hovered = true, + WidgetEvent::MouseLeave => self.resp.hovered = false, _ => {} }; EventResponse::Bubble diff --git a/goryak/src/imagebutton.rs b/goryak/src/imagebutton.rs index 5b565ad3..1e465dcb 100644 --- a/goryak/src/imagebutton.rs +++ b/goryak/src/imagebutton.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::time::Instant; use yakui_core::event::{EventInterest, EventResponse, WidgetEvent}; @@ -21,7 +22,7 @@ pub struct ImageButton { pub color: Color, pub hover_color: Color, pub active_color: Color, - pub tooltip: &'static str, + pub tooltip: Cow<'static, str>, } impl ImageButton { @@ -32,7 +33,7 @@ impl ImageButton { color: Color::WHITE, hover_color: Color::WHITE, active_color: Color::WHITE, - tooltip: "", + tooltip: Cow::Borrowed(""), } } @@ -49,7 +50,7 @@ impl ImageButton { color, hover_color, active_color, - tooltip: "", + tooltip: Cow::Borrowed(""), } } @@ -62,7 +63,7 @@ pub fn primary_image_button( texture: TextureId, size: Vec2, enabled: bool, - tooltip: &'static str, + tooltip: impl Into>, ) -> Response { let (default_col, hover_col) = if enabled { let c = primary().lerp(&Color::WHITE, 0.3); @@ -79,7 +80,7 @@ pub fn image_button( color: Color, hover_color: Color, active_color: Color, - tooltip: &'static str, + tooltip: impl Into>, ) -> Response { ImageButton { texture: Some(texture), @@ -87,7 +88,7 @@ pub fn image_button( color, hover_color, active_color, - tooltip, + tooltip: tooltip.into(), } .show() } @@ -135,7 +136,7 @@ impl Widget for ImageButtonWidget { if self.show_tooltip { round_rect(5.0, primary(), || { padxy(5.0, 4.0, || { - textc(on_primary(), self.props.tooltip); + textc(on_primary(), self.props.tooltip.clone()); }); }); } diff --git a/goryak/src/util.rs b/goryak/src/util.rs index 7219702e..7f9460e2 100644 --- a/goryak/src/util.rs +++ b/goryak/src/util.rs @@ -9,7 +9,7 @@ use yakui_core::{context, CrossAxisAlignment, MainAxisSize, Response, WidgetId}; use yakui_widgets::util::widget; use yakui_widgets::widgets::{Button, List, ListResponse, Pad, PadResponse, Text}; -use crate::{on_primary, on_secondary, primary, secondary}; +use crate::{on_primary, on_secondary, primary, secondary, DEFAULT_FONT_SIZE}; pub fn checkbox_value(v: &mut bool, color: Color, label: &'static str) { minrow(5.0, || { @@ -52,9 +52,18 @@ pub fn padx(x: f32, children: impl FnOnce()) -> Response { Pad::horizontal(x).show(children) } +pub fn titlec(c: Color, text: impl Into>) { + let mut t = Text::label(text.into()); + t.style.color = c; + t.style.font_size = DEFAULT_FONT_SIZE + 6.0; + t.padding = Pad::vertical(3.0); + t.show(); +} + pub fn textc(c: Color, text: impl Into>) { let mut t = Text::label(text.into()); t.style.color = c; + t.style.font_size = DEFAULT_FONT_SIZE; t.padding = Pad::all(0.0); t.show(); } diff --git a/native_app/src/game_loop.rs b/native_app/src/game_loop.rs index 9caf5ccf..b7b41382 100644 --- a/native_app/src/game_loop.rs +++ b/native_app/src/game_loop.rs @@ -14,6 +14,7 @@ use crate::gui::{render_oldgui, ExitState, FollowEntity, GuiState, UiTextures}; use crate::inputmap::{Bindings, InputAction, InputMap}; use crate::newgui; use crate::newgui::terraforming::TerraformingResource; +use crate::newgui::toolbox::building; use crate::newgui::windows::settings::{manage_settings, Settings}; use crate::newgui::{render_newgui, TimeAlways, Tool}; use crate::rendering::{InstancedRender, MapRenderOptions, MapRenderer, OrbitCamera}; @@ -79,6 +80,7 @@ impl engine::framework::State for State { } defer!(log::info!("finished init of game loop")); + building::do_icons(ctx, &mut uiworld); let me = Self { uiw: uiworld, @@ -97,7 +99,6 @@ impl engine::framework::State for State { profiling::scope!("game_loop::update"); self.uiw.write::().0 += ctx.delta; - newgui::do_icons(&mut ctx.gfx, &mut self.uiw, &mut ctx.yakui); { let mut timings = self.uiw.write::(); timings.engine_time.add_value(ctx.engine_time); diff --git a/native_app/src/gui/hud.rs b/native_app/src/gui/hud.rs index a5686f04..433b5425 100644 --- a/native_app/src/gui/hud.rs +++ b/native_app/src/gui/hud.rs @@ -3,12 +3,10 @@ use std::time::Instant; use egui::load::SizedTexture; use egui::{Align2, Color32, Context, Frame, Id, Response, RichText, Style, Ui, Widget, Window}; -use geom::{Polygon, Vec2}; -use prototypes::{ - prototypes_iter, BuildingGen, FreightStationPrototype, GoodsCompanyPrototype, ItemID, Money, -}; +use geom::Vec2; +use prototypes::{prototypes_iter, BuildingGen, FreightStationPrototype, ItemID, Money}; use simulation::economy::Government; -use simulation::map::{BuildingKind, LanePatternBuilder, MapProject, Zone}; +use simulation::map::{BuildingKind, LanePatternBuilder, MapProject}; use simulation::world_command::WorldCommand; use simulation::Simulation; @@ -129,10 +127,7 @@ pub fn toolbox(ui: &Context, uiworld: &UiWorld, _sim: &Simulation) { let toolbox_w = 85.0; - let tools = [ - ("buildings", Tool::SpecialBuilding), - ("traintool", Tool::Train), - ]; + let tools = [("traintool", Tool::Train)]; Window::new("Toolbox") .min_width(toolbox_w) @@ -181,28 +176,6 @@ pub fn toolbox(ui: &Context, uiworld: &UiWorld, _sim: &Simulation) { *uiworld.write::() = Tool::Train; } - /* - if ui.button_with_size("Trainstation", [rbw, 30.0]) { - *uiworld.write::() = Tool::SpecialBuilding; - - let h = LanePatternBuilder::new().rail(true).n_lanes(1).width(); - - uiworld.write::().opt = Some(SpecialBuildKind { - make: Box::new(move |args, commands| { - let d = args.obb.axis()[0].z(0.0) * 0.5; - let off = args.obb.axis()[1].z(0.0).normalize_to(h * 0.5 + 10.0); - commands.map_build_trainstation( - args.mpos - d - off, - args.mpos + d - off, - ); - }), - w: h + 15.0, - h: 230.0, - asset: "trainstation.glb".to_string(), - road_snap: false, - }); - }*/ - for proto in prototypes_iter::() { let mut freightstation = RichText::new(&proto.label); if *uiworld.read::() == Tool::SpecialBuilding { @@ -249,110 +222,6 @@ pub fn toolbox(ui: &Context, uiworld: &UiWorld, _sim: &Simulation) { } }); } - - let building_select_w = 200.0; - - if matches!(*uiworld.read::(), Tool::SpecialBuilding) { - Window::new("Buildings") - .min_width(building_select_w) - .default_height(500.0f32.min(h * 0.5)) - .vscroll(true) - .fixed_pos([w - toolbox_w - building_select_w, h * 0.5 - 100.0]) - .title_bar(true) - .collapsible(false) - .resizable(false) - .show(ui, |ui| { - let mut cur_build = uiworld.write::(); - - let mut picked_descr = None; - ui.style_mut().spacing.interact_size = [building_select_w - 5.0, 35.0].into(); - - for descr in prototypes_iter::() { - let cur_kind = cur_build.opt.as_ref().map(|x| &x.asset); - - let mut name = RichText::new(&descr.name); - if Some(&descr.asset) == cur_kind { - picked_descr = Some(descr); - name = name.strong(); - }; - if ui.button(name).clicked() || cur_build.opt.is_none() { - let bkind = BuildingKind::GoodsCompany(descr.id); - let bgen = descr.bgen; - let has_zone = descr.zone.is_some(); - cur_build.opt = Some(SpecialBuildKind { - road_snap: true, - make: Box::new(move |args| { - vec![WorldCommand::MapBuildSpecialBuilding { - pos: args.obb, - kind: bkind, - gen: bgen, - zone: has_zone.then(|| { - Zone::new( - Polygon::from(args.obb.corners.as_slice()), - Vec2::X, - ) - }), - connected_road: args.connected_road, - }] - }), - size: descr.size, - asset: descr.asset.clone(), - }); - } - } - - let bdescrpt_w = 180.0; - - if let Some(descr) = picked_descr { - Window::new("Building description") - .default_width(bdescrpt_w) - .auto_sized() - .fixed_pos([ - w - toolbox_w - building_select_w - bdescrpt_w, - h * 0.5 - 30.0, - ]) - .hscroll(false) - .title_bar(true) - .collapsible(false) - .resizable(false) - .show(ui.ctx(), |ui| { - ui.label(format!("workers: {}", descr.n_workers)); - - if let Some(ref recipe) = descr.recipe { - ui.add_space(10.0); - if !recipe.consumption.is_empty() { - ui.label("consumption:"); - for item in &recipe.consumption { - item_icon(ui, uiworld, item.id, item.amount); - } - ui.add_space(10.0); - } - if !recipe.production.is_empty() { - ui.label("production:"); - for item in &recipe.production { - item_icon(ui, uiworld, item.id, item.amount); - } - ui.add_space(10.0); - } - ui.label(format!("time: {}", recipe.duration)); - ui.label(format!( - "storage multiplier: {}", - recipe.storage_multiplier - )); - } - - if let Some(p) = descr.power_consumption { - ui.add_space(10.0); - ui.label(format!("Power: {}", p)); - } - if let Some(p) = descr.power_production { - ui.add_space(10.0); - ui.label(format!("Power production: {}", p)); - } - }); - } - }); - } } pub fn item_icon(ui: &mut Ui, uiworld: &UiWorld, id: ItemID, multiplier: i32) -> Response { diff --git a/native_app/src/gui/mod.rs b/native_app/src/gui/mod.rs index 93c49a31..7978a512 100644 --- a/native_app/src/gui/mod.rs +++ b/native_app/src/gui/mod.rs @@ -71,4 +71,8 @@ impl UiTextures { pub fn try_get(&self, name: &str) -> Option { self.textures.get(name).map(TextureHandle::id) } + + pub fn try_get_yakui(&self, name: &str) -> Option { + self.yakui_textures.get(name).copied() + } } diff --git a/native_app/src/init.rs b/native_app/src/init.rs index 0d00f189..11430914 100644 --- a/native_app/src/init.rs +++ b/native_app/src/init.rs @@ -10,12 +10,12 @@ use crate::newgui::roadbuild::RoadBuildResource; use crate::newgui::roadeditor::RoadEditorResource; use crate::newgui::specialbuilding::SpecialBuildingResource; use crate::newgui::terraforming::TerraformingResource; +use crate::newgui::toolbox::building::BuildingIcons; use crate::newgui::windows::economy::EconomyState; use crate::newgui::windows::settings::{Settings, SettingsState}; use crate::newgui::zoneedit::ZoneEditState; use crate::newgui::{ - ErrorTooltip, IconTextures, InspectedBuilding, InspectedEntity, PotentialCommands, TimeAlways, - Tool, + ErrorTooltip, InspectedBuilding, InspectedEntity, PotentialCommands, TimeAlways, Tool, }; use crate::rendering::immediate::{ImmediateDraw, ImmediateSound}; use crate::uiworld::{ReceivedCommands, UiWorld}; @@ -64,7 +64,7 @@ pub fn init() { register_resource_noserialize::(); register_resource_noserialize::(); register_resource_noserialize::(); - register_resource_noserialize::(); + register_resource_noserialize::(); } pub struct InitFunc { diff --git a/native_app/src/newgui/hud.rs b/native_app/src/newgui/hud.rs index 314676bb..6c90393d 100644 --- a/native_app/src/newgui/hud.rs +++ b/native_app/src/newgui/hud.rs @@ -1,8 +1,9 @@ -use goryak::minrow; -use ordered_float::OrderedFloat; use std::time::Instant; -use yakui::widgets::Pad; -use yakui::{image, reflow, Alignment, Color, Dim2, Vec2}; + +use goryak::{image_button, minrow, on_secondary_container, textc}; +use ordered_float::OrderedFloat; +use prototypes::ItemID; +use yakui::{reflow, Alignment, Color, Dim2, Vec2}; use simulation::map_dynamic::ElectricityFlow; use simulation::Simulation; @@ -12,12 +13,11 @@ use crate::newgui::hud::menu::menu_bar; use crate::newgui::hud::time_controls::time_controls; use crate::newgui::hud::toolbox::new_toolbox; use crate::newgui::windows::settings::Settings; -use crate::newgui::IconTextures; use crate::uiworld::{SaveLoadState, UiWorld}; mod menu; mod time_controls; -mod toolbox; +pub mod toolbox; pub mod windows; /// Root GUI entrypoint @@ -35,21 +35,6 @@ pub fn render_newgui(uiworld: &UiWorld, sim: &Simulation) { menu_bar(uiworld, sim); uiworld.write::().windows.render(uiworld, sim); time_controls(uiworld, sim); - goryak::Window { - title: "Building icons", - pad: Pad::all(5.0), - radius: 5.0, - opened: &mut true, - } - .show(|| { - minrow(5.0, || { - let ids = &uiworld.read::().ids; - - for id in ids.values() { - image(*id, Vec2::splat(96.0)); - } - }); - }); }); //goryak::debug_layout(); } @@ -111,3 +96,34 @@ fn power_errors(uiworld: &UiWorld, sim: &Simulation) { } } } + +pub fn item_icon_yakui(uiworld: &UiWorld, id: ItemID, multiplier: i32) { + let item = id.prototype(); + minrow(5.0, || { + if let Some(id) = uiworld + .read::() + .try_get_yakui(&format!("icon/{}", item.name)) + { + if image_button( + id, + Vec2::new(32.0, 32.0), + Color::WHITE, + Color::WHITE, + Color::WHITE, + "", + ) + .hovering + { + reflow(Alignment::CENTER, Dim2::ZERO, || { + textc( + on_secondary_container(), + format!("{} x{}", item.name, multiplier), + ); + }); + } + } else { + textc(on_secondary_container(), format!("- {} ", &item.label)); + } + textc(on_secondary_container(), format!("x{multiplier}")) + }); +} diff --git a/native_app/src/newgui/hud/toolbox/building.rs b/native_app/src/newgui/hud/toolbox/building.rs new file mode 100644 index 00000000..c8a4945c --- /dev/null +++ b/native_app/src/newgui/hud/toolbox/building.rs @@ -0,0 +1,303 @@ +use common::FastMap; +use engine::{Context, TextureBuilder}; +use yakui::widgets::List; +use yakui::{ + reflow, use_state, Alignment, Color, CrossAxisAlignment, Dim2, MainAxisAlignment, TextureId, + Vec2, +}; + +use crate::newgui::item_icon_yakui; +use engine::wgpu::TextureFormat; +use geom::{Camera, Degrees, Polygon, Vec3}; +use goryak::{ + blur_bg, fixed_spacer, image_button, is_hovered, mincolumn, minrow, on_secondary_container, + padxy, pivot, primary, secondary_container, textc, titlec, +}; +use prototypes::{ + prototypes_iter, BuildingPrototypeID, GoodsCompanyID, GoodsCompanyPrototype, Prototype, + RenderAsset, +}; +use simulation::map::{BuildingKind, Zone}; +use simulation::world_command::WorldCommand; +use std::path::PathBuf; +use std::time::Instant; + +use crate::newgui::specialbuilding::{SpecialBuildKind, SpecialBuildingResource}; +use crate::uiworld::UiWorld; + +pub fn special_building_properties(uiw: &UiWorld) { + let mut state = uiw.write::(); + let icons = uiw.read::(); + + padxy(0.0, 10.0, || { + let mut l = List::row(); + l.main_axis_alignment = MainAxisAlignment::Center; + l.cross_axis_alignment = CrossAxisAlignment::Center; + l.item_spacing = 10.0; + l.show(|| { + let tooltip_active = use_state(|| Option::<(GoodsCompanyID, Instant)>::None); + for descr in prototypes_iter::() { + let Some(tex_id) = icons.ids.get(&descr.parent().id) else { + continue; + }; + + minrow(0.0, || { + let default_col = Color::WHITE; + let hover_col = primary(); + let active_col = default_col.with_alpha(0.5); + + let resp = image_button( + *tex_id, + Vec2::splat(64.0), + default_col, + hover_col, + active_col, + "", + ); + + if resp.hovering { + tooltip_active.set(Some((descr.id, Instant::now()))); + } + + if tooltip_active + .borrow() + .map(|(id, last)| id == descr.id && last.elapsed().as_secs_f32() < 0.2) + .unwrap_or(false) + { + reflow(Alignment::TOP_CENTER, Dim2::pixels(0.0, -20.0), || { + pivot(Alignment::BOTTOM_CENTER, || { + if is_hovered(|| { + blur_bg(secondary_container().with_alpha(0.5), 10.0, || { + padxy(10.0, 10.0, || { + mincolumn(|| { + titlec(on_secondary_container(), &descr.label); + textc( + on_secondary_container(), + format!("workers: {}", descr.n_workers), + ); + + if let Some(ref recipe) = descr.recipe { + fixed_spacer((0.0, 10.0)); + if !recipe.consumption.is_empty() { + textc( + on_secondary_container(), + "consumption:", + ); + for item in &recipe.consumption { + item_icon_yakui( + uiw, + item.id, + item.amount, + ); + } + fixed_spacer((0.0, 10.0)); + } + if !recipe.production.is_empty() { + textc( + on_secondary_container(), + "production:", + ); + for item in &recipe.production { + item_icon_yakui( + uiw, + item.id, + item.amount, + ); + } + fixed_spacer((0.0, 10.0)); + } + textc( + on_secondary_container(), + format!("time: {}", recipe.duration), + ); + textc( + on_secondary_container(), + format!( + "storage multiplier: {}", + recipe.storage_multiplier + ), + ); + } + + if let Some(p) = descr.power_consumption { + fixed_spacer((0.0, 10.0)); + textc( + on_secondary_container(), + format!("Power: {}", p), + ); + } + if let Some(p) = descr.power_production { + fixed_spacer((0.0, 10.0)); + textc( + on_secondary_container(), + format!("Power production: {}", p), + ); + } + }); + }); + }); + }) + .hovered + { + tooltip_active.set(Some((descr.id, Instant::now()))); + } + }); + }); + } + + if resp.clicked || state.opt.is_none() { + let bkind = BuildingKind::GoodsCompany(descr.id); + let bgen = descr.bgen; + let has_zone = descr.zone.is_some(); + state.opt = Some(SpecialBuildKind { + road_snap: true, + make: Box::new(move |args| { + vec![WorldCommand::MapBuildSpecialBuilding { + pos: args.obb, + kind: bkind, + gen: bgen, + zone: has_zone.then(|| { + Zone::new( + Polygon::from(args.obb.corners.as_slice()), + geom::Vec2::X, + ) + }), + connected_road: args.connected_road, + }] + }), + size: descr.size, + asset: descr.asset.clone(), + }); + } + }); + } + }); + }); + + /* + let bdescrpt_w = 180.0; + + if let Some(descr) = picked_descr { + Window::new("Building description") + .default_width(bdescrpt_w) + .auto_sized() + .fixed_pos([ + w - toolbox_w - building_select_w - bdescrpt_w, + h * 0.5 - 30.0, + ]) + .hscroll(false) + .title_bar(true) + .collapsible(false) + .resizable(false) + .show(ui.ctx(), |ui| { + ui.label(format!("workers: {}", descr.n_workers)); + + if let Some(ref recipe) = descr.recipe { + ui.add_space(10.0); + if !recipe.consumption.is_empty() { + ui.label("consumption:"); + for item in &recipe.consumption { + item_icon(ui, uiworld, item.id, item.amount); + } + ui.add_space(10.0); + } + if !recipe.production.is_empty() { + ui.label("production:"); + for item in &recipe.production { + item_icon(ui, uiworld, item.id, item.amount); + } + ui.add_space(10.0); + } + ui.label(format!("time: {}", recipe.duration)); + ui.label(format!("storage multiplier: {}", recipe.storage_multiplier)); + } + + if let Some(p) = descr.power_consumption { + ui.add_space(10.0); + ui.label(format!("Power: {}", p)); + } + if let Some(p) = descr.power_production { + ui.add_space(10.0); + ui.label(format!("Power production: {}", p)); + } + }); + */ +} + +#[derive(Default)] +pub struct BuildingIcons { + ids: FastMap, +} + +pub fn do_icons(ctx: &mut Context, uiw: &UiWorld) { + let mut state = uiw.write::(); + + let mut cam = Camera::new(Vec3::new(0.0, 0.0, 0.0), 256.0, 256.0); + + cam.fovy = 30.0; + cam.pitch = Degrees(35.0).into(); + cam.yaw = Degrees(-130.0).into(); + + state.ids.clear(); + + let gfx = &mut ctx.gfx; + + for building in prototypes::BuildingPrototype::iter() { + if state.ids.contains_key(&building.id) { + continue; + } + if let RenderAsset::Sprite { ref path } = building.asset { + let t = gfx.texture(path, "building icon"); + let tex_id = ctx.yakui.add_texture(&t); + state.ids.insert(building.id, tex_id); + continue; + } + + let RenderAsset::Mesh { ref path } = building.asset else { + continue; + }; + let cache_path = PathBuf::from(format!( + "assets/generated/building_icons/{}.png", + building.id.hash() + )); + //if std::fs::metadata(&cache_path).is_ok() { + // let t = TextureBuilder::from_path(&cache_path).build(&gfx.device, &gfx.queue); + // let tex_id = yakui.add_texture(&t); + // state.ids.insert(building.id, tex_id); + // continue; + //} + + let Ok(mesh) = gfx.mesh(path.as_ref()) else { + continue; + }; + + let t = TextureBuilder::empty(128, 128, 1, TextureFormat::Rgba8UnormSrgb) + .with_label("building icon") + .with_usage( + engine::wgpu::TextureUsages::COPY_DST + | engine::wgpu::TextureUsages::COPY_SRC + | engine::wgpu::TextureUsages::RENDER_ATTACHMENT + | engine::wgpu::TextureUsages::TEXTURE_BINDING, + ) + .build_no_queue(&gfx.device); + + let t_msaa = TextureBuilder::empty(128, 128, 1, TextureFormat::Rgba8UnormSrgb) + .with_label("building icon msaa") + .with_usage(engine::wgpu::TextureUsages::RENDER_ATTACHMENT) + .with_sample_count(4) + .build_no_queue(&gfx.device); + + let aabb3 = mesh.lods[0].aabb3; + cam.pos = aabb3.center(); + cam.dist = aabb3.ll.distance(aabb3.ur); + cam.update(); + + mesh.render_to_texture(&cam, gfx, &t, &t_msaa); + + t.save_to_file(&gfx.device, &gfx.queue, cache_path, 0); + + let tex_id = ctx.yakui.add_texture(&t); + + state.ids.insert(building.id, tex_id); + } +} diff --git a/native_app/src/newgui/hud/toolbox/mod.rs b/native_app/src/newgui/hud/toolbox/mod.rs index 5e7a59b3..1498dd56 100644 --- a/native_app/src/newgui/hud/toolbox/mod.rs +++ b/native_app/src/newgui/hud/toolbox/mod.rs @@ -16,9 +16,10 @@ use crate::inputmap::{InputAction, InputMap}; use crate::newgui::Tool; use crate::uiworld::UiWorld; -mod roadbuild; -mod roadedit; -mod terraforming; +pub mod building; +pub mod roadbuild; +pub mod roadedit; +pub mod terraforming; pub fn new_toolbox(uiworld: &UiWorld, sim: &Simulation) { if uiworld @@ -78,7 +79,9 @@ fn tool_properties(uiw: &UiWorld, _sim: &Simulation) -> bool { Tool::RoadEditor => { roadedit::roadedit_properties(uiw); } - Tool::SpecialBuilding => {} + Tool::SpecialBuilding => { + building::special_building_properties(uiw); + } Tool::Train => {} Tool::Terraforming => { terraforming::terraform_properties(uiw); diff --git a/native_app/src/newgui/hud/toolbox/roadbuild.rs b/native_app/src/newgui/hud/toolbox/roadbuild.rs index 426ab2c3..1f1d6912 100644 --- a/native_app/src/newgui/hud/toolbox/roadbuild.rs +++ b/native_app/src/newgui/hud/toolbox/roadbuild.rs @@ -130,7 +130,7 @@ pub fn roadbuild_properties(uiw: &UiWorld) { default_col, hover_col, primary(), - label, + *label, ) .clicked { diff --git a/native_app/src/newgui/hud/toolbox/roadedit.rs b/native_app/src/newgui/hud/toolbox/roadedit.rs index 619c29d5..17ad198b 100644 --- a/native_app/src/newgui/hud/toolbox/roadedit.rs +++ b/native_app/src/newgui/hud/toolbox/roadedit.rs @@ -38,7 +38,7 @@ pub fn roadedit_properties(uiw: &UiWorld) { texs.get_yakui(icon), Vec2::new(64.0, 64.0), enabled, - label, + *label, ) .clicked { diff --git a/native_app/src/newgui/hud/toolbox/terraforming.rs b/native_app/src/newgui/hud/toolbox/terraforming.rs index 0fc67d35..c7f9262b 100644 --- a/native_app/src/newgui/hud/toolbox/terraforming.rs +++ b/native_app/src/newgui/hud/toolbox/terraforming.rs @@ -39,7 +39,7 @@ pub fn terraform_properties(uiw: &UiWorld) { texs.get_yakui(icon), Vec2::new(64.0, 64.0), enabled, - label, + *label, ) .clicked { @@ -67,7 +67,7 @@ pub fn terraform_properties(uiw: &UiWorld) { texs.get_yakui(icon), Vec2::new(64.0, 64.0), enabled, - label, + *label, ) .clicked { @@ -105,7 +105,7 @@ pub fn terraform_properties(uiw: &UiWorld) { texs.get_yakui(icon), Vec2::new(64.0, 64.0), enabled, - label, + *label, ) .clicked { diff --git a/native_app/src/newgui/mod.rs b/native_app/src/newgui/mod.rs index 3cc7bddf..7a88a08e 100644 --- a/native_app/src/newgui/mod.rs +++ b/native_app/src/newgui/mod.rs @@ -1,16 +1,8 @@ use crate::uiworld::UiWorld; -use common::FastMap; -use engine::wgpu::TextureFormat; -use engine::yakui::YakuiWrapper; -use engine::{GfxContext, TextureBuilder}; -use geom::{Camera, Degrees, Vec3}; -use prototypes::{BuildingPrototypeID, RenderAsset}; use simulation::map::BuildingID; use simulation::world_command::WorldCommand; use simulation::{AnyEntity, Simulation}; use std::borrow::Cow; -use std::path::PathBuf; -use yakui::TextureId; mod hud; mod tools; @@ -18,75 +10,6 @@ mod tools; pub use hud::*; pub use tools::*; -#[derive(Default)] -pub struct IconTextures { - ids: FastMap, -} - -pub fn do_icons(gfx: &mut GfxContext, uiw: &mut UiWorld, yakui: &mut YakuiWrapper) { - let mut state = uiw.write::(); - - let mut cam = Camera::new(Vec3::new(0.0, 0.0, 0.0), 256.0, 256.0); - - cam.fovy = 30.0; - cam.pitch = Degrees(35.0).into(); - cam.yaw = Degrees(-130.0).into(); - - state.ids.clear(); - - for building in prototypes::BuildingPrototype::iter() { - let RenderAsset::Mesh { ref path } = building.asset else { - continue; - }; - if state.ids.contains_key(&building.id) { - continue; - } - let cache_path = PathBuf::from(format!( - "assets/generated/building_icons/{}.png", - building.id.hash() - )); - //if std::fs::metadata(&cache_path).is_ok() { - // let t = TextureBuilder::from_path(&cache_path).build(&gfx.device, &gfx.queue); - // let tex_id = yakui.add_texture(&t); - // state.ids.insert(building.id, tex_id); - // continue; - //} - - let Ok(mesh) = gfx.mesh(path.as_ref()) else { - continue; - }; - - let t = TextureBuilder::empty(128, 128, 1, TextureFormat::Rgba8UnormSrgb) - .with_label("building icon") - .with_usage( - engine::wgpu::TextureUsages::COPY_DST - | engine::wgpu::TextureUsages::COPY_SRC - | engine::wgpu::TextureUsages::RENDER_ATTACHMENT - | engine::wgpu::TextureUsages::TEXTURE_BINDING, - ) - .build_no_queue(&gfx.device); - - let t_msaa = TextureBuilder::empty(128, 128, 1, TextureFormat::Rgba8UnormSrgb) - .with_label("building icon msaa") - .with_usage(engine::wgpu::TextureUsages::RENDER_ATTACHMENT) - .with_sample_count(4) - .build_no_queue(&gfx.device); - - let aabb3 = mesh.lods[0].aabb3; - cam.pos = aabb3.center(); - cam.dist = aabb3.ll.distance(aabb3.ur); - cam.update(); - - mesh.render_to_texture(&cam, gfx, &t, &t_msaa); - - t.save_to_file(&gfx.device, &gfx.queue, cache_path, 0); - - let tex_id = yakui.add_texture(&t); - - state.ids.insert(building.id, tex_id); - } -} - pub fn run_ui_systems(sim: &Simulation, uiworld: &UiWorld) { profiling::scope!("gui::run_ui_systems"); bulldozer::bulldozer(sim, uiworld); diff --git a/native_app/src/newgui/tools/specialbuilding.rs b/native_app/src/newgui/tools/specialbuilding.rs index 159379bb..b8d19c46 100644 --- a/native_app/src/newgui/tools/specialbuilding.rs +++ b/native_app/src/newgui/tools/specialbuilding.rs @@ -83,19 +83,26 @@ pub fn specialbuilding(sim: &Simulation, uiworld: &UiWorld) { let half_diag = 0.5 * size.diag(); let hover_obb = OBB::new(mpos.xy(), state.rotation.vec2(), size.w, size.h); - let mut draw = |obb, red| { - let p = asset.to_string(); + let mut draw = |obb: OBB, red| { let col = if red { simulation::colors().gui_danger.adjust_luminosity(1.3) } else { simulation::colors().gui_primary.adjust_luminosity(1.5) }; - if p.ends_with(".png") || p.ends_with(".jpg") { - draw.textured_obb(obb, p, mpos.z + 0.1).color(col); - } else if p.ends_with(".glb") { - draw.mesh(p, obb.center().z(mpos.z), obb.axis()[0].normalize().z0()) + match asset { + RenderAsset::Mesh { path } => { + draw.mesh( + path.to_string_lossy().to_string(), + obb.center().z(mpos.z), + obb.axis()[0].normalize().z0(), + ) .color(col); + } + RenderAsset::Sprite { path } => { + draw.textured_obb(obb, path.to_string_lossy().to_string(), mpos.z + 0.1) + .color(col); + } } }; diff --git a/prototypes/src/lib.rs b/prototypes/src/lib.rs index 9748a5cf..cbfe22cb 100644 --- a/prototypes/src/lib.rs +++ b/prototypes/src/lib.rs @@ -34,22 +34,26 @@ pub trait Prototype: 'static + Sized { fn id(&self) -> Self::ID; /// The parent of the prototype - fn parent(&self) -> Option<&Self::Parent>; - - /// util function to recursively insert the parents of this prototype into the prototypes lists - fn insert_parents(&self, prototypes: &mut Prototypes) { - if let Some(p) = self.parent() { - Self::Parent::storage_mut(prototypes).insert(p.id(), p.clone()); - p.insert_parents(prototypes); - } - } + fn parent(&self) -> &Self::Parent; } /// A concrete prototype is a prototype that has a static storage and ordering (it is not virtual) pub trait ConcretePrototype: Prototype + Clone { + const HAS_PARENT: bool = true; + fn ordering(prototypes: &Prototypes) -> &[Self::ID]; fn storage(prototypes: &Prototypes) -> &TransparentMap; fn storage_mut(prototypes: &mut Prototypes) -> &mut TransparentMap; + + /// util function to recursively insert the parents of this prototype into the prototypes lists + fn insert_parents(&self, prototypes: &mut Prototypes) { + let p = self.parent(); + if !::HAS_PARENT { + return; + } + Self::Parent::storage_mut(prototypes).insert(p.id(), p.clone()); + p.insert_parents(prototypes); + } } /// The unique ID of a prototype @@ -73,12 +77,14 @@ impl Prototype for NoParent { unreachable!() } - fn parent(&self) -> Option<&Self::Parent> { - None + fn parent(&self) -> &Self::Parent { + self } } impl ConcretePrototype for NoParent { + const HAS_PARENT: bool = false; + fn ordering(_prototypes: &Prototypes) -> &[Self::ID] { &[] } @@ -90,6 +96,8 @@ impl ConcretePrototype for NoParent { fn storage_mut(_prototypes: &mut Prototypes) -> &mut TransparentMap { unreachable!() } + + fn insert_parents(&self, _prototypes: &mut Prototypes) {} } static mut PROTOTYPES: Option<&'static Prototypes> = None; diff --git a/prototypes/src/macros.rs b/prototypes/src/macros.rs index 50ff1c45..7f8a52b2 100644 --- a/prototypes/src/macros.rs +++ b/prototypes/src/macros.rs @@ -100,7 +100,7 @@ macro_rules! gen_prototypes { $crate::PrototypeLoadError::PrototypeLuaError(_type_str.to_string(), table.get::<_, String>("name").unwrap(), e) })?; - <$t as $crate::Prototype>::insert_parents(&proto, self); + <$t as $crate::ConcretePrototype>::insert_parents(&proto, self); if let Some(v) = self.$name.insert((&proto.name).into(), proto) { log::warn!("duplicate {} with name: {}", <$t as $crate::Prototype>::NAME, v.name); diff --git a/prototypes/src/prototypes/building.rs b/prototypes/src/prototypes/building.rs index faad24f1..c1cc129b 100644 --- a/prototypes/src/prototypes/building.rs +++ b/prototypes/src/prototypes/building.rs @@ -58,8 +58,8 @@ impl Prototype for BuildingPrototype { self.id } - fn parent(&self) -> Option<&Self::Parent> { - None + fn parent(&self) -> &Self::Parent { + &NoParent } } diff --git a/prototypes/src/prototypes/colors.rs b/prototypes/src/prototypes/colors.rs index b5ecbf71..7467efe9 100644 --- a/prototypes/src/prototypes/colors.rs +++ b/prototypes/src/prototypes/colors.rs @@ -69,8 +69,8 @@ impl Prototype for ColorsPrototype { self.id } - fn parent(&self) -> Option<&Self::Parent> { - None + fn parent(&self) -> &Self::Parent { + &NoParent } } diff --git a/prototypes/src/prototypes/freightstation.rs b/prototypes/src/prototypes/freightstation.rs index 88c66bc0..04c57861 100644 --- a/prototypes/src/prototypes/freightstation.rs +++ b/prototypes/src/prototypes/freightstation.rs @@ -34,8 +34,8 @@ impl Prototype for FreightStationPrototype { self.id } - fn parent(&self) -> Option<&Self::Parent> { - None + fn parent(&self) -> &Self::Parent { + &NoParent } } diff --git a/prototypes/src/prototypes/goods_company.rs b/prototypes/src/prototypes/goods_company.rs index 71d2bf61..7b447035 100644 --- a/prototypes/src/prototypes/goods_company.rs +++ b/prototypes/src/prototypes/goods_company.rs @@ -48,8 +48,8 @@ impl Prototype for GoodsCompanyPrototype { self.id } - fn parent(&self) -> Option<&Self::Parent> { - Some(&self.base) + fn parent(&self) -> &Self::Parent { + &self.base } } diff --git a/prototypes/src/prototypes/item.rs b/prototypes/src/prototypes/item.rs index a5e333c4..041e1fe3 100644 --- a/prototypes/src/prototypes/item.rs +++ b/prototypes/src/prototypes/item.rs @@ -29,8 +29,8 @@ impl Prototype for ItemPrototype { self.id } - fn parent(&self) -> Option<&Self::Parent> { - None + fn parent(&self) -> &Self::Parent { + &NoParent } } diff --git a/prototypes/src/prototypes/leisure.rs b/prototypes/src/prototypes/leisure.rs index 66c94572..c150599c 100644 --- a/prototypes/src/prototypes/leisure.rs +++ b/prototypes/src/prototypes/leisure.rs @@ -34,8 +34,8 @@ impl Prototype for LeisurePrototype { self.id } - fn parent(&self) -> Option<&Self::Parent> { - Some(&self.base) + fn parent(&self) -> &Self::Parent { + &self.base } } diff --git a/prototypes/src/prototypes/mod.rs b/prototypes/src/prototypes/mod.rs index 2a2c9be2..32947758 100644 --- a/prototypes/src/prototypes/mod.rs +++ b/prototypes/src/prototypes/mod.rs @@ -95,7 +95,7 @@ impl crate::Prototype for PrototypeBase { fn id(&self) -> Self::ID {} - fn parent(&self) -> Option<&Self::Parent> { - None + fn parent(&self) -> &Self::Parent { + &crate::NoParent } } diff --git a/prototypes/src/prototypes/solar.rs b/prototypes/src/prototypes/solar.rs index c352d3aa..3557ff43 100644 --- a/prototypes/src/prototypes/solar.rs +++ b/prototypes/src/prototypes/solar.rs @@ -24,8 +24,8 @@ impl Prototype for SolarPanelPrototype { self.id } - fn parent(&self) -> Option<&Self::Parent> { - Some(&self.base) + fn parent(&self) -> &Self::Parent { + &self.base } }