From 2a9324d5f9d361fc2b7b6a2ea345ab42f5929a4b Mon Sep 17 00:00:00 2001 From: Emory Petermann Date: Thu, 24 Dec 2020 18:20:57 -0600 Subject: [PATCH] proof that UI can work --- Cargo.toml | 5 +- amethyst_assets/src/loader.rs | 5 +- amethyst_core/src/dispatcher.rs | 1 + amethyst_rendy/src/bundle.rs | 3 - amethyst_ui/src/bundle.rs | 27 +- amethyst_ui/src/format.rs | 2 +- amethyst_ui/src/glyphs.rs | 1095 ++++++++++---------- amethyst_ui/src/pass.rs | 5 +- book/src/pong-tutorial/pong-tutorial-03.md | 2 +- examples/auto_fov/main.rs | 2 +- examples/ui_from_code/main.rs | 40 +- src/lib.rs | 1 + 12 files changed, 623 insertions(+), 565 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 170f49e0e1..d77a4c445f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ serde-diff = { git = "https://github.com/amethyst/serde-diff", branch = "v0.4.0" [features] default = ["parallel", "renderer"] -optional = ["audio", "network", "locale", "ui"] +optional = ["audio", "network", "locale", "ui", "utils"] vulkan = ["amethyst_rendy/vulkan"] metal = ["amethyst_rendy/metal"] @@ -41,6 +41,7 @@ audio = ["amethyst_audio"] #gltf = ["amethyst_gltf", "amethyst_animation"] locale = ["amethyst_locale"] network = ["amethyst_network"] +utils = ["amethyst_utils"] renderer = ["amethyst_rendy"] @@ -95,7 +96,7 @@ amethyst_locale = { path = "amethyst_locale", version = "0.15.3", optional = tru amethyst_rendy = { path = "amethyst_rendy", version = "0.15.3", features = ["window"], optional = true } amethyst_input = { path = "amethyst_input", version = "0.15.3" } amethyst_ui = { path = "amethyst_ui", version = "0.15.3", optional = true } -amethyst_utils = { path = "amethyst_utils", version = "0.15.3" } +amethyst_utils = { path = "amethyst_utils", version = "0.15.3", optional = true } amethyst_window = { path = "amethyst_window", version = "0.15.3" } #amethyst_tiles = { path = "amethyst_tiles", version = "0.15.3", optional = true } winit = { git = "https://git@github.com/rust-windowing/winit", rev = "38fccebe1fbc4226c75d6180e5317bd93c024951", features = ["serde"] } diff --git a/amethyst_assets/src/loader.rs b/amethyst_assets/src/loader.rs index 70e7547e21..5e28612e20 100644 --- a/amethyst_assets/src/loader.rs +++ b/amethyst_assets/src/loader.rs @@ -167,9 +167,11 @@ pub struct LoaderWithStorage { ref_sender: Sender, ref_receiver: Receiver, handle_allocator: Arc, - indirection_table: IndirectionTable, + pub indirection_table: IndirectionTable, } +impl LoaderWithStorage {} + impl Default for LoaderWithStorage { fn default() -> Self { let (tx, rx) = unbounded(); @@ -509,6 +511,7 @@ where for<'a> Intermediate: 'static + Deserialize<'a> + TypeUuid + Send, ProcessorSystem: System<'static> + Default + 'static, { + log::debug!("Creating asset type: {:x?}", Asset::UUID); AssetType { data_uuid: AssetTypeId(Intermediate::UUID), asset_uuid: AssetTypeId(Asset::UUID), diff --git a/amethyst_core/src/dispatcher.rs b/amethyst_core/src/dispatcher.rs index ff828beeb7..c9bf64de1a 100644 --- a/amethyst_core/src/dispatcher.rs +++ b/amethyst_core/src/dispatcher.rs @@ -71,6 +71,7 @@ impl<'a> DispatcherBuilder { /// Adds a system to the schedule. pub fn add_system + 'a>(&mut self, system: Box) -> &mut Self { let s: &'a mut S = Box::leak(system); + log::debug!("Building system"); self.items.push(DispatcherItem::System(s.build())); self } diff --git a/amethyst_rendy/src/bundle.rs b/amethyst_rendy/src/bundle.rs index f6770de7a9..0c43e6f103 100644 --- a/amethyst_rendy/src/bundle.rs +++ b/amethyst_rendy/src/bundle.rs @@ -82,9 +82,6 @@ impl SystemBundle for RenderingBundle { ) -> Result<(), Error> { resources.insert(ActiveCamera::default()); - // TODO: make sure that all renderer-specific systems run after game code - //builder.flush(); TODO: flush legion here? - for plugin in &mut self.plugins { plugin.on_build(world, resources, builder)?; } diff --git a/amethyst_ui/src/bundle.rs b/amethyst_ui/src/bundle.rs index eacf969a8b..8815e3ab77 100644 --- a/amethyst_ui/src/bundle.rs +++ b/amethyst_ui/src/bundle.rs @@ -2,8 +2,10 @@ use std::marker::PhantomData; +use amethyst_assets::{AssetProcessorSystem, AssetStorage, DefaultLoader, ProcessingQueue}; use amethyst_core::{ecs::*, shrev::EventChannel}; use amethyst_error::Error; +use amethyst_rendy::types::DefaultBackend; use derive_new::new; use winit::event::Event; @@ -11,6 +13,8 @@ use crate::{ button::{ui_button_action_retrigger_event_system, UiButtonSystem}, drag::DragWidgetSystem, event::UiMouseSystem, + format::FontData, + glyphs::{GlyphTextureData, GlyphTextureProcessorSystem}, layout::UiTransformSystem, resize::ResizeSystem, selection::{SelectionKeyboardSystem, SelectionMouseSystem}, @@ -18,8 +22,8 @@ use crate::{ sound::{ui_sound_event_retrigger_system, UiSoundSystem}, text::TextEditingMouseSystem, text_editing::TextEditingInputSystem, - BlinkSystem, CachedSelectionOrderResource, UiButtonAction, UiEvent, UiLabel, UiPlaySoundAction, - WidgetId, Widgets, + BlinkSystem, CachedSelectionOrderResource, FontAsset, UiButtonAction, UiEvent, UiLabel, + UiPlaySoundAction, WidgetId, Widgets, }; /// UI bundle @@ -46,11 +50,28 @@ where resources: &mut Resources, builder: &mut DispatcherBuilder, ) -> Result<(), Error> { + log::debug!("Adding UI Resources"); resources.insert(EventChannel::::new()); resources.insert(EventChannel::::new()); resources.insert(Widgets::::new()); resources.insert(CachedSelectionOrderResource::default()); + resources.insert(ProcessingQueue::::default()); + let storage = { + let loader = resources.get::().unwrap(); + + AssetStorage::::new(loader.indirection_table.clone()) + }; + builder.add_system(Box::new(AssetProcessorSystem::::default())); + + resources.insert(ProcessingQueue::::default()); + builder.add_system(Box::new( + GlyphTextureProcessorSystem::::default(), + )); + + resources.insert(storage); + + log::debug!("Creating UI EventChannel Readers"); let ui_btn_reader = resources .get_mut::>() .unwrap() @@ -83,6 +104,8 @@ where .get_mut::>() .unwrap() .register_reader(); + + log::debug!("Adding UI Systems to Dispatcher"); builder .add_system(Box::new(UiTransformSystem::new())) .add_system(Box::new(UiMouseSystem::new())) diff --git a/amethyst_ui/src/format.rs b/amethyst_ui/src/format.rs index 14e63afcfc..6336f7c92b 100644 --- a/amethyst_ui/src/format.rs +++ b/amethyst_ui/src/format.rs @@ -1,4 +1,4 @@ -use amethyst_assets::{Asset, Format, Handle, ProcessableAsset, ProcessingState}; +use amethyst_assets::{Asset, Format, ProcessableAsset, ProcessingState}; use amethyst_error::{format_err, Error, ResultExt}; use glyph_brush::rusttype::Font; use serde::{Deserialize, Serialize}; diff --git a/amethyst_ui/src/glyphs.rs b/amethyst_ui/src/glyphs.rs index e4052fa62f..b8b0f67134 100644 --- a/amethyst_ui/src/glyphs.rs +++ b/amethyst_ui/src/glyphs.rs @@ -1,15 +1,12 @@ //! Module containing the system managing glyphbrush state for visible UI Text components. -use std::{ - collections::{HashMap, HashSet}, - marker::PhantomData, - ops::Deref, -}; +use std::{collections::HashMap, marker::PhantomData, ops::Deref}; use amethyst_assets::{ - AssetStorage, DefaultLoader, Handle, LoadHandle, ProcessingQueue, ProcessingState, + AssetHandle, AssetStorage, DefaultLoader, Handle, LoadHandle, Loader, ProcessingQueue, + ProcessingState, }; -use amethyst_core::{ecs::*, Hidden, HiddenPropagate}; +use amethyst_core::{dispatcher::ThreadLocalSystem, ecs::*, Hidden, HiddenPropagate}; use amethyst_rendy::{ rendy::{ command::QueueId, @@ -18,12 +15,16 @@ use amethyst_rendy::{ texture::{pixel::R8Unorm, TextureBuilder}, }, resources::Tint, + types::DefaultBackend, Backend, Texture, }; use glyph_brush::{ rusttype::Scale, BrushAction, BrushError, BuiltInLineBreaker, FontId, GlyphBrush, GlyphBrushBuilder, GlyphCruncher, Layout, LineBreak, LineBreaker, SectionText, VariedSection, }; +use log::debug; +use serde::Deserialize; +use type_uuid::TypeUuid; use unicode_segmentation::UnicodeSegmentation; use crate::{ @@ -83,7 +84,6 @@ impl LineBreaker for CustomLineBreaker { } /// Manages the text editing cursor create, deletion and position. -#[allow(missing_debug_implementations)] pub struct UiGlyphsSystem { glyph_brush: GlyphBrush<'static, (u32, UiArgs)>, glyph_entity_cache: HashMap, @@ -93,17 +93,78 @@ pub struct UiGlyphsSystem { impl Default for UiGlyphsSystem { fn default() -> Self { + debug!("Initializing UiGlyphsSystem"); Self { glyph_brush: GlyphBrushBuilder::using_fonts(vec![]) .initial_cache_size((512, 512)) .build(), - ..Default::default() + marker: PhantomData, + fonts_map: HashMap::new(), + glyph_entity_cache: HashMap::new(), } } } -use amethyst_assets::{AssetHandle, Loader}; -use amethyst_rendy::types::DefaultBackend; +use amethyst_assets::register_asset_type; +use derivative::Derivative; + +#[derive(Debug, Clone, TypeUuid, Deserialize)] +#[uuid = "36e442d3-b957-4155-8f3b-01f580931226"] +pub struct GlyphTextureData { + w: u32, + h: u32, +} + +#[derive(Debug, Derivative)] +#[derivative(Default(bound = ""))] +pub(crate) struct GlyphTextureProcessorSystem { + pub(crate) _marker: std::marker::PhantomData, +} + +impl System<'static> for GlyphTextureProcessorSystem { + fn build(&'static mut self) -> Box { + use hal::format::{Component as C, Swizzle}; + + Box::new( + SystemBuilder::new("TextureProcessorSystem") + .write_resource::>() + .write_resource::>() + .read_resource::() + .write_resource::>() + .build( + move |commands, world, (processing_queue, tex_storage, queue, factory), _| { + processing_queue.process(tex_storage, |b| { + log::debug!("Creating new glyph texture with size ({}, {})", b.w, b.h); + + TextureBuilder::new() + .with_kind(hal::image::Kind::D2(b.w, b.h, 1, 1)) + .with_view_kind(hal::image::ViewKind::D2) + .with_data_width(b.w) + .with_data_height(b.h) + .with_data(vec![R8Unorm { repr: [0] }; (b.w * b.h) as _]) + // This swizzle is required when working with `R8Unorm` on metal. + // Glyph texture is biased towards 1.0 using "color_bias" attribute instead. + .with_swizzle(Swizzle(C::Zero, C::Zero, C::Zero, C::R)) + .build( + ImageState { + queue: **queue, + stage: hal::pso::PipelineStage::FRAGMENT_SHADER, + access: hal::image::Access::SHADER_READ, + layout: hal::image::Layout::General, + }, + factory, + ) + .map(B::wrap_texture) + .map(ProcessingState::Loaded) + .map_err(|e| e.into()) + }); + + tex_storage.process_custom_drop(|_| {}); + }, + ), + ) + } +} impl System<'static> for UiGlyphsSystem { fn build(&'static mut self) -> Box { @@ -165,535 +226,542 @@ impl System<'static> for UiGlyphsSystem { }) }; - use hal::format::{Component as C, Swizzle}; + if let Some(mut tex) = + tex_storage.get(glyph_tex).and_then(B::unwrap_texture) + { + let (mut glyph_world, mut else_world) = + world.split_for_query(glyphs_query); + let (mut selected_world, mut else_world) = + else_world.split_for_query(selected_query); + + texts_not_hidden_query_with_optional_editing.for_each_mut( + &mut else_world, + |(entity, transform, ui_text, editing, tint)| { + ui_text.cached_glyphs.clear(); + let font_asset = + font_storage.get(&ui_text.font).map(|font| font.0.clone()); + + if let FontState::NotFound = self + .fonts_map + .entry(ui_text.font.load_handle()) + .or_insert(FontState::NotFound) + { + if let Some(font) = font_storage.get(&ui_text.font) { + log::debug!("Adding font to glyph brush."); + let new_font = FontState::Ready( + self.glyph_brush.add_font(font.0.clone()), + ); + self.fonts_map + .insert(ui_text.font.load_handle(), new_font); + } + } - tex_queue.process(tex_storage, |b| { - log::trace!("Creating new glyph texture with size ({}, {})", b.w, b.h); + let font_lookup = + self.fonts_map.get(&ui_text.font.load_handle()).unwrap(); - TextureBuilder::new() - .with_kind(hal::image::Kind::D2(b.w, b.h, 1, 1)) - .with_view_kind(hal::image::ViewKind::D2) - .with_data_width(b.w) - .with_data_height(b.h) - .with_data(vec![R8Unorm { repr: [0] }; (b.w * b.h) as _]) - // This swizzle is required when working with `R8Unorm` on metal. - // Glyph texture is biased towards 1.0 using "color_bias" attribute instead. - .with_swizzle(Swizzle(C::Zero, C::Zero, C::Zero, C::R)) - .build( - ImageState { - queue, - stage: hal::pso::PipelineStage::FRAGMENT_SHADER, - access: hal::image::Access::SHADER_READ, - layout: hal::image::Layout::General, - }, - factory, - ) - .map(B::wrap_texture) - .map(ProcessingState::Loaded) - .map_err(|e| e.into()) - }); + if let (Some(font_id), Some(font_asset)) = + (font_lookup.id(), font_asset) + { + let tint_color = tint.map_or([1., 1., 1., 1.], |t| { + let (r, g, b, a) = t.0.into_components(); + [r, g, b, a] + }); - tex_storage.process_custom_drop(|_| {}); + let base_color = mul_blend(&ui_text.color, &tint_color); - let mut tex = tex_storage - .get(glyph_tex) - .and_then(B::unwrap_texture) - .expect("Glyph texture is created synchronously"); - - let (mut glyph_world, mut else_world) = world.split_for_query(glyphs_query); - let (mut selected_world, mut else_world) = - else_world.split_for_query(selected_query); - texts_not_hidden_query_with_optional_editing.for_each_mut( - &mut else_world, - |(entity, transform, ui_text, editing, tint)| { - ui_text.cached_glyphs.clear(); - let font_asset = - font_storage.get(&ui_text.font).map(|font| font.0.clone()); - if let FontState::NotFound = self - .fonts_map - .entry(ui_text.font.load_handle()) - .or_insert(FontState::NotFound) - { - if let Some(font) = font_storage.get(&ui_text.font) { - let new_font = FontState::Ready( - self.glyph_brush.add_font(font.0.clone()), - ); - self.fonts_map.insert(ui_text.font.load_handle(), new_font); - } - } + let scale = Scale::uniform(ui_text.font_size); - let font_lookup = - self.fonts_map.get(&ui_text.font.load_handle()).unwrap(); - - if let (Some(font_id), Some(font_asset)) = - (font_lookup.id(), font_asset) - { - let tint_color = tint.map_or([1., 1., 1., 1.], |t| { - let (r, g, b, a) = t.0.into_components(); - [r, g, b, a] - }); - - let base_color = mul_blend(&ui_text.color, &tint_color); - - let scale = Scale::uniform(ui_text.font_size); - - let text = match (ui_text.password, editing) { - (false, None) => vec![SectionText { - text: &ui_text.text, - scale, - color: base_color, - font_id, - }], - (false, Some(sel)) => { - if let Some((start, end)) = - selection_span(sel, &ui_text.text) - { - vec![ - SectionText { - text: &ui_text.text[..start], + let text = match (ui_text.password, editing) { + (false, None) => vec![SectionText { + text: &ui_text.text, + scale, + color: base_color, + font_id, + }], + (false, Some(sel)) => { + if let Some((start, end)) = + selection_span(sel, &ui_text.text) + { + vec![ + SectionText { + text: &ui_text.text[..start], + scale, + color: base_color, + font_id, + }, + SectionText { + text: &ui_text.text[start..end], + scale, + color: mul_blend( + &sel.selected_text_color, + &tint_color, + ), + font_id, + }, + SectionText { + text: &ui_text.text[end..], + scale, + color: base_color, + font_id, + }, + ] + } else { + vec![SectionText { + text: &ui_text.text, scale, color: base_color, font_id, - }, - SectionText { - text: &ui_text.text[start..end], + }] + } + } + (true, None) => { + let string_len = + ui_text.text.graphemes(true).count(); + password_sections(string_len) + .map(|text| SectionText { + text, scale, - color: mul_blend( + color: base_color, + font_id, + }) + .collect() + } + (true, Some(sel)) => { + let string_len = + ui_text.text.graphemes(true).count(); + let pos = sel.cursor_position; + let pos_highlight = + sel.cursor_position + sel.highlight_vector; + let start = pos.min(pos_highlight) as usize; + let to_end = + pos.max(pos_highlight) as usize - start; + let rest = string_len - start - to_end; + [ + (start, base_color), + ( + to_end, + mul_blend( &sel.selected_text_color, &tint_color, ), - font_id, - }, - SectionText { - text: &ui_text.text[end..], - scale, - color: base_color, - font_id, - }, + ), + (rest, base_color), ] - } else { - vec![SectionText { - text: &ui_text.text, - scale, - color: base_color, - font_id, - }] - } - } - (true, None) => { - let string_len = ui_text.text.graphemes(true).count(); - password_sections(string_len) - .map(|text| SectionText { - text, - scale, - color: base_color, - font_id, + .iter() + .cloned() + .flat_map(|(subsection_len, color)| { + password_sections(subsection_len).map( + move |text| SectionText { + text, + scale, + color, + font_id, + }, + ) }) .collect() - } - (true, Some(sel)) => { - let string_len = ui_text.text.graphemes(true).count(); - let pos = sel.cursor_position; - let pos_highlight = - sel.cursor_position + sel.highlight_vector; - let start = pos.min(pos_highlight) as usize; - let to_end = pos.max(pos_highlight) as usize - start; - let rest = string_len - start - to_end; - [ - (start, base_color), - ( - to_end, - mul_blend( - &sel.selected_text_color, - &tint_color, - ), + } + }; + + let layout = match ui_text.line_mode { + LineMode::Single => Layout::SingleLine { + line_breaker: CustomLineBreaker::None, + h_align: ui_text.align.horizontal_align(), + v_align: ui_text.align.vertical_align(), + }, + LineMode::Wrap => Layout::Wrap { + line_breaker: CustomLineBreaker::BuiltIn( + BuiltInLineBreaker::UnicodeLineBreaker, ), - (rest, base_color), - ] - .iter() - .cloned() - .flat_map(|(subsection_len, color)| { - password_sections(subsection_len).map(move |text| { - SectionText { - text, - scale, - color, - font_id, - } - }) - }) - .collect() - } - }; - - let layout = match ui_text.line_mode { - LineMode::Single => Layout::SingleLine { - line_breaker: CustomLineBreaker::None, - h_align: ui_text.align.horizontal_align(), - v_align: ui_text.align.vertical_align(), - }, - LineMode::Wrap => Layout::Wrap { - line_breaker: CustomLineBreaker::BuiltIn( - BuiltInLineBreaker::UnicodeLineBreaker, - ), - h_align: ui_text.align.horizontal_align(), - v_align: ui_text.align.vertical_align(), - }, - }; + h_align: ui_text.align.horizontal_align(), + v_align: ui_text.align.vertical_align(), + }, + }; - let next_z = - if let Some(val) = self.glyph_entity_cache.keys().last() { + let next_z = if let Some(val) = + self.glyph_entity_cache.keys().last() + { val + 1 } else { 0 }; - self.glyph_entity_cache.insert(next_z, *entity); - - let section = VariedSection { - // Needs a recenter because we are using [-0.5,0.5] for the mesh - // instead of the expected [0,1] - screen_position: ( - transform.pixel_x - + transform.pixel_width - * ui_text.align.norm_offset().0, - // invert y because layout calculates it in reverse - -(transform.pixel_y - + transform.pixel_height - * ui_text.align.norm_offset().1), - ), - bounds: (transform.pixel_width, transform.pixel_height), - // There is no other way to inject some glyph metadata than using Z. - // Fortunately depth is not required, so this slot is instead used to - // distinguish computed glyphs indented to be used for various entities - // FIXME: This will be a problem because entities are now u64 and not u32.... - z: next_z as f32, - layout: Default::default(), // overriden on queue - text, - }; - - // `GlyphBrush::glyphs_custom_layout` does not return glyphs for invisible - // characters. - // - // - // - // For support, see: - // - // - let mut nonempty_cached_glyphs = self - .glyph_brush - .glyphs_custom_layout(§ion, &layout) - .map(|g| { - let pos = g.position(); - let advance_width = - g.unpositioned().h_metrics().advance_width; - CachedGlyph { - x: pos.x, - y: -pos.y, - advance_width, - } - }); + self.glyph_entity_cache.insert(next_z, *entity); + + let section = VariedSection { + // Needs a recenter because we are using [-0.5,0.5] for the mesh + // instead of the expected [0,1] + screen_position: ( + transform.pixel_x + + transform.pixel_width + * ui_text.align.norm_offset().0, + // invert y because layout calculates it in reverse + -(transform.pixel_y + + transform.pixel_height + * ui_text.align.norm_offset().1), + ), + bounds: (transform.pixel_width, transform.pixel_height), + // There is no other way to inject some glyph metadata than using Z. + // Fortunately depth is not required, so this slot is instead used to + // distinguish computed glyphs indented to be used for various entities + // FIXME: This will be a problem because entities are now u64 and not u32.... + z: next_z as f32, + layout: Default::default(), // overriden on queue + text, + }; + + // `GlyphBrush::glyphs_custom_layout` does not return glyphs for invisible + // characters. + // + // + // + // For support, see: + // + // + let mut nonempty_cached_glyphs = self + .glyph_brush + .glyphs_custom_layout(§ion, &layout) + .map(|g| { + let pos = g.position(); + let advance_width = + g.unpositioned().h_metrics().advance_width; + CachedGlyph { + x: pos.x, + y: -pos.y, + advance_width, + } + }); - let mut last_cached_glyph: Option = None; - let all_glyphs = ui_text.text.chars().filter_map(|c| { - if c.is_whitespace() { - let (x, y) = if let Some(last_cached_glyph) = + let mut last_cached_glyph: Option = None; + let all_glyphs = ui_text.text.chars().filter_map(|c| { + if c.is_whitespace() { + let (x, y) = if let Some(last_cached_glyph) = + last_cached_glyph + { + let x = last_cached_glyph.x + + last_cached_glyph.advance_width; + let y = last_cached_glyph.y; + (x, y) + } else { + (0.0, 0.0) + }; + + let advance_width = font_asset + .glyph(c) + .scaled(scale) + .h_metrics() + .advance_width; + + let cached_glyph = CachedGlyph { + x, + y, + advance_width, + }; + last_cached_glyph = Some(cached_glyph); last_cached_glyph - { - let x = last_cached_glyph.x - + last_cached_glyph.advance_width; - let y = last_cached_glyph.y; - (x, y) } else { - (0.0, 0.0) - }; - - let advance_width = font_asset - .glyph(c) - .scaled(scale) - .h_metrics() - .advance_width; - - let cached_glyph = CachedGlyph { - x, - y, - advance_width, - }; - last_cached_glyph = Some(cached_glyph); - last_cached_glyph - } else { - last_cached_glyph = nonempty_cached_glyphs.next(); - last_cached_glyph - } - }); - ui_text.cached_glyphs.extend(all_glyphs); + last_cached_glyph = nonempty_cached_glyphs.next(); + last_cached_glyph + } + }); + ui_text.cached_glyphs.extend(all_glyphs); - self.glyph_brush.queue_custom_layout(section, &layout); - } - }, - ); - - loop { - let action = self.glyph_brush.process_queued( - |rect, data| unsafe { - log::trace!("Upload glyph image at {:?}", rect); - factory - .upload_image( - tex.image().clone(), - rect.width(), - rect.height(), - hal::image::SubresourceLayers { - aspects: hal::format::Aspects::COLOR, - level: 0, - layers: 0..1, - }, - hal::image::Offset { - x: rect.min.x as _, - y: rect.min.y as _, - z: 0, - }, - hal::image::Extent { - width: rect.width(), - height: rect.height(), - depth: 1, - }, - data, - ImageState { - queue, - stage: hal::pso::PipelineStage::FRAGMENT_SHADER, - access: hal::image::Access::SHADER_READ, - layout: hal::image::Layout::General, - }, - ImageState { - queue, - stage: hal::pso::PipelineStage::FRAGMENT_SHADER, - access: hal::image::Access::SHADER_READ, - layout: hal::image::Layout::General, - }, - ) - .unwrap(); - }, - move |glyph| { - // The glyph's Z parameter smuggles a key to retrieve entity, so glyphs can be associated - // for rendering as part of specific components. - let mut uv = glyph.tex_coords; - let bounds_max_x = glyph.bounds.max.x as f32; - let bounds_max_y = glyph.bounds.max.y as f32; - let bounds_min_x = glyph.bounds.min.x as f32; - let bounds_min_y = glyph.bounds.min.y as f32; - let mut coords_max_x = glyph.pixel_coords.max.x as f32; - let mut coords_max_y = glyph.pixel_coords.max.y as f32; - let mut coords_min_x = glyph.pixel_coords.min.x as f32; - let mut coords_min_y = glyph.pixel_coords.min.y as f32; - - // Glyph out of bounds, trim the quad - if coords_max_x > bounds_max_x { - let old_width = coords_max_x - coords_min_x; - coords_max_x = bounds_max_x; - uv.max.x = uv.min.x - + (uv.max.x - uv.min.x) * (coords_max_x - coords_min_x) - / old_width; - } - if coords_min_x < bounds_min_x { - let old_width = coords_max_x - coords_min_x; - coords_min_x = bounds_min_x; - uv.min.x = uv.max.x - - (uv.max.x - uv.min.x) * (coords_max_x - coords_min_x) - / old_width; + self.glyph_brush.queue_custom_layout(section, &layout); } - if coords_max_y > bounds_max_y { - let old_height = coords_max_y - coords_min_y; - coords_max_y = bounds_max_y; - uv.max.y = uv.min.y - + (uv.max.y - uv.min.y) * (coords_max_y - coords_min_y) - / old_height; - } - if coords_min_y < bounds_min_y { - let old_height = coords_max_y - coords_min_y; - coords_min_y = bounds_min_y; - uv.min.y = uv.max.y - - (uv.max.y - uv.min.y) * (coords_max_y - coords_min_y) - / old_height; - } - - let coords = [ - (coords_max_x + coords_min_x) * 0.5, - -(coords_max_y + coords_min_y) * 0.5, - ]; - let dims = [ - (coords_max_x - coords_min_x), - (coords_max_y - coords_min_y), - ]; - let tex_coord_bounds = [uv.min.x, uv.min.y, uv.max.x, uv.max.y]; - log::trace!("Push glyph for cached glyph entity {}", glyph.z); - ( - glyph.z as u32, - UiArgs { - coords: coords.into(), - dimensions: dims.into(), - tex_coord_bounds: tex_coord_bounds.into(), - color: glyph.color.into(), - color_bias: [1., 1., 1., 0.].into(), - }, - ) }, ); - match action { - Ok(BrushAction::Draw(vertices)) => { - log::trace!("Updating glyph data, len {}", vertices.len()); - // entity ids are guaranteed to be in the same order as queued - let mut glyph_ctr = 0; + loop { + let action = self.glyph_brush.process_queued( + |rect, data| unsafe { + log::trace!("Upload glyph image at {:?}", rect); + factory + .upload_image( + tex.image().clone(), + rect.width(), + rect.height(), + hal::image::SubresourceLayers { + aspects: hal::format::Aspects::COLOR, + level: 0, + layers: 0..1, + }, + hal::image::Offset { + x: rect.min.x as _, + y: rect.min.y as _, + z: 0, + }, + hal::image::Extent { + width: rect.width(), + height: rect.height(), + depth: 1, + }, + data, + ImageState { + queue, + stage: hal::pso::PipelineStage::FRAGMENT_SHADER, + access: hal::image::Access::SHADER_READ, + layout: hal::image::Layout::General, + }, + ImageState { + queue, + stage: hal::pso::PipelineStage::FRAGMENT_SHADER, + access: hal::image::Access::SHADER_READ, + layout: hal::image::Layout::General, + }, + ) + .unwrap(); + }, + move |glyph| { + // The glyph's Z parameter smuggles a key to retrieve entity, so glyphs can be associated + // for rendering as part of specific components. + let mut uv = glyph.tex_coords; + let bounds_max_x = glyph.bounds.max.x as f32; + let bounds_max_y = glyph.bounds.max.y as f32; + let bounds_min_x = glyph.bounds.min.x as f32; + let bounds_min_y = glyph.bounds.min.y as f32; + let mut coords_max_x = glyph.pixel_coords.max.x as f32; + let mut coords_max_y = glyph.pixel_coords.max.y as f32; + let mut coords_min_x = glyph.pixel_coords.min.x as f32; + let mut coords_min_y = glyph.pixel_coords.min.y as f32; + + // Glyph out of bounds, trim the quad + if coords_max_x > bounds_max_x { + let old_width = coords_max_x - coords_min_x; + coords_max_x = bounds_max_x; + uv.max.x = uv.min.x + + (uv.max.x - uv.min.x) + * (coords_max_x - coords_min_x) + / old_width; + } + if coords_min_x < bounds_min_x { + let old_width = coords_max_x - coords_min_x; + coords_min_x = bounds_min_x; + uv.min.x = uv.max.x + - (uv.max.x - uv.min.x) + * (coords_max_x - coords_min_x) + / old_width; + } + if coords_max_y > bounds_max_y { + let old_height = coords_max_y - coords_min_y; + coords_max_y = bounds_max_y; + uv.max.y = uv.min.y + + (uv.max.y - uv.min.y) + * (coords_max_y - coords_min_y) + / old_height; + } + if coords_min_y < bounds_min_y { + let old_height = coords_max_y - coords_min_y; + coords_min_y = bounds_min_y; + uv.min.y = uv.max.y + - (uv.max.y - uv.min.y) + * (coords_max_y - coords_min_y) + / old_height; + } - // make sure to erase all glyphs, even if not queued this frame + let coords = [ + (coords_max_x + coords_min_x) * 0.5, + -(coords_max_y + coords_min_y) * 0.5, + ]; + let dims = [ + (coords_max_x - coords_min_x), + (coords_max_y - coords_min_y), + ]; + let tex_coord_bounds = + [uv.min.x, uv.min.y, uv.max.x, uv.max.y]; + log::trace!( + "Push glyph for cached glyph entity {}", + glyph.z + ); + ( + glyph.z as u32, + UiArgs { + coords: coords.into(), + dimensions: dims.into(), + tex_coord_bounds: tex_coord_bounds.into(), + color: glyph.color.into(), + color_bias: [1., 1., 1., 0.].into(), + }, + ) + }, + ); - for (_, glyph_data) in glyphs_query.iter_mut(&mut glyph_world) { - glyph_data.vertices.clear(); - glyph_data.sel_vertices.clear(); - } + match action { + Ok(BrushAction::Draw(vertices)) => { + log::trace!("Updating glyph data, len {}", vertices.len()); + // entity ids are guaranteed to be in the same order as queued + let mut glyph_ctr = 0; - texts_not_hidden_query_with_optional_editing.for_each_mut( - &mut else_world, - |(entity, transform, ui_text, editing, tint)| { - let len = vertices[glyph_ctr..] - .iter() - .take_while(|(id, _)| { - self.glyph_entity_cache.get(id).unwrap() - == entity - }) - .count(); - let entity_verts = vertices[glyph_ctr..glyph_ctr + len] - .iter() - .map(|v| v.1); - glyph_ctr += len; + // make sure to erase all glyphs, even if not queued this frame - if let Some((_, glyph_data)) = - glyphs_query.get_mut(&mut glyph_world, *entity).ok() - { - glyph_data.vertices.extend(entity_verts); - } else { - commands.add_component( - *entity, - UiGlyphs { - vertices: entity_verts.collect(), - sel_vertices: vec![], - cursor_pos: (0., 0.), - height: 0., - space_width: 0., - }, - ); - } + for (_, glyph_data) in + glyphs_query.iter_mut(&mut glyph_world) + { + glyph_data.vertices.clear(); + glyph_data.sel_vertices.clear(); + } - if let Some(editing) = editing { - let font = font_storage.get(&ui_text.font).expect( - "Font with rendered glyphs must be loaded", - ); - let scale = Scale::uniform(ui_text.font_size); - let v_metrics = font.0.v_metrics(scale); - let height = v_metrics.ascent - v_metrics.descent; - let offset = - (v_metrics.ascent + v_metrics.descent) * 0.5; - let total_len = ui_text.cached_glyphs.len(); - let pos = editing.cursor_position; - let pos_highlight = editing.cursor_position - + editing.highlight_vector; - let start = (pos.min(pos_highlight) as usize) - .min(total_len); - let end = (pos.max(pos_highlight) as usize) - .min(total_len); - - let tint_color = - tint.map_or([1., 1., 1., 1.], |t| { - let (r, g, b, a) = t.0.into_components(); - [r, g, b, a] - }); - let bg_color = editing.selected_background_color; - let bg_color = if selected_query - .get_mut(&mut selected_world, *entity) - .is_ok() + texts_not_hidden_query_with_optional_editing.for_each_mut( + &mut else_world, + |(entity, transform, ui_text, editing, tint)| { + let len = vertices[glyph_ctr..] + .iter() + .take_while(|(id, _)| { + self.glyph_entity_cache.get(id).unwrap() + == entity + }) + .count(); + let entity_verts = vertices + [glyph_ctr..glyph_ctr + len] + .iter() + .map(|v| v.1); + glyph_ctr += len; + + if let Some((_, glyph_data)) = glyphs_query + .get_mut(&mut glyph_world, *entity) + .ok() { - bg_color + glyph_data.vertices.extend(entity_verts); } else { - mul_blend(&bg_color, &[0.5, 0.5, 0.5, 0.5]) - }; - let bg_color = mul_blend(&tint_color, &bg_color); + commands.add_component( + *entity, + UiGlyphs { + vertices: entity_verts.collect(), + sel_vertices: vec![], + cursor_pos: (0., 0.), + height: 0., + space_width: 0., + }, + ); + } - let iter = ui_text.cached_glyphs[start..end] - .iter() - .map(|g| UiArgs { - coords: [ - g.x + g.advance_width * 0.5, - g.y + offset, - ] - .into(), - dimensions: [g.advance_width, height] - .into(), - tex_coord_bounds: [0., 0., 1., 1.].into(), - color: bg_color.into(), - color_bias: [1., 1., 1., 0.].into(), - }); - - if let Ok(glyph_data) = - glyphs_query.get_mut(&mut glyph_world, *entity) + if let Some(editing) = editing { + if let Some(font) = + font_storage.get(&ui_text.font) + { + let scale = + Scale::uniform(ui_text.font_size); + let v_metrics = font.0.v_metrics(scale); + let height = + v_metrics.ascent - v_metrics.descent; + let offset = (v_metrics.ascent + + v_metrics.descent) + * 0.5; + let total_len = ui_text.cached_glyphs.len(); + let pos = editing.cursor_position; + let pos_highlight = editing.cursor_position + + editing.highlight_vector; + let start = (pos.min(pos_highlight) + as usize) + .min(total_len); + let end = (pos.max(pos_highlight) as usize) + .min(total_len); + + let tint_color = + tint.map_or([1., 1., 1., 1.], |t| { + let (r, g, b, a) = + t.0.into_components(); + [r, g, b, a] + }); + let bg_color = + editing.selected_background_color; + let bg_color = if selected_query + .get_mut(&mut selected_world, *entity) + .is_ok() + { + bg_color + } else { + mul_blend( + &bg_color, + &[0.5, 0.5, 0.5, 0.5], + ) + }; + let bg_color = + mul_blend(&tint_color, &bg_color); + + let iter = ui_text.cached_glyphs + [start..end] + .iter() + .map(|g| UiArgs { + coords: [ + g.x + g.advance_width * 0.5, + g.y + offset, + ] + .into(), + dimensions: [ + g.advance_width, + height, + ] + .into(), + tex_coord_bounds: [0., 0., 1., 1.] + .into(), + color: bg_color.into(), + color_bias: [1., 1., 1., 0.].into(), + }); + + if let Ok(glyph_data) = glyphs_query + .get_mut(&mut glyph_world, *entity) + { + glyph_data.1.sel_vertices.extend(iter); + glyph_data.1.height = height; + glyph_data.1.space_width = font + .0 + .glyph(' ') + .scaled(scale) + .h_metrics() + .advance_width; + update_cursor_position( + glyph_data.1, + ui_text, + transform, + pos as usize, + offset, + ); + } + } + } + }, + ); + break; + } + Ok(BrushAction::ReDraw) => { + not_hidden_glyphs_query_with_editing.for_each_mut( + world, + |(_, glyph_data, transform, ui_text, editing)| { + if let Some(font) = font_storage.get(&ui_text.font) { - glyph_data.1.sel_vertices.extend(iter); - glyph_data.1.height = height; - glyph_data.1.space_width = font - .0 - .glyph(' ') - .scaled(scale) - .h_metrics() - .advance_width; + let scale = Scale::uniform(ui_text.font_size); + let v_metrics = font.0.v_metrics(scale); + let pos = editing.cursor_position; + let offset = (v_metrics.ascent + + v_metrics.descent) + * 0.5; update_cursor_position( - glyph_data.1, + glyph_data, ui_text, transform, pos as usize, offset, ); } - } - }, - ); - break; - } - Ok(BrushAction::ReDraw) => { - not_hidden_glyphs_query_with_editing.for_each_mut( - world, - |(_, glyph_data, transform, ui_text, editing)| { - let font = font_storage - .get(&ui_text.font) - .expect("Font with rendered glyphs must be loaded"); - let scale = Scale::uniform(ui_text.font_size); - let v_metrics = font.0.v_metrics(scale); - let pos = editing.cursor_position; - let offset = - (v_metrics.ascent + v_metrics.descent) * 0.5; - update_cursor_position( - glyph_data, - ui_text, - transform, - pos as usize, - offset, - ); - }, - ); - break; - } - Err(BrushError::TextureTooSmall { suggested: (w, h) }) => { - // Replace texture in asset storage. No handles have to be updated. - let glyph_tex: Handle = loader.load_from_data( - GlyphTextureData { w, h }, - (), - &tex_queue, - ); - - tex = tex_storage - .get(&glyph_tex) - .and_then(B::unwrap_texture) - .unwrap(); - self.glyph_brush.resize_texture(w, h); + }, + ); + break; + } + Err(BrushError::TextureTooSmall { suggested: (w, h) }) => { + // Replace texture in asset storage. No handles have to be updated. + let glyph_tex: Handle = loader.load_from_data( + GlyphTextureData { w, h }, + (), + &tex_queue, + ); + + tex = tex_storage + .get(&glyph_tex) + .and_then(B::unwrap_texture) + .unwrap(); + self.glyph_brush.resize_texture(w, h); + } } } } @@ -722,45 +790,6 @@ fn update_cursor_position( }; } -use serde::Deserialize; -use type_uuid::TypeUuid; - -#[derive(Debug, Clone, TypeUuid, Deserialize)] -#[uuid = "36e442d3-b957-4155-8f3b-01f580931226"] -struct GlyphTextureData { - w: u32, - h: u32, -} - -amethyst_assets::register_asset_type!(GlyphTextureData => Texture; GlyphTextureProcessorSystem); - -use derivative::Derivative; - -/// Asset processing system for `Texture` asset type. -#[derive(Debug, Derivative)] -#[derivative(Default(bound = ""))] -pub struct GlyphTextureProcessorSystem { - pub(crate) _marker: std::marker::PhantomData, -} - -impl System<'static> for GlyphTextureProcessorSystem { - fn build(&'static mut self) -> Box { - Box::new( - SystemBuilder::new("GlyphTextureProcessor") - .write_resource::>() - .write_resource::>() - .read_resource::() - .write_resource::>() - .build( - move |commands, - world, - (processing_queue, texture_storage, queue_id, factory), - _| {}, - ), - ) - } -} - fn selection_span(editing: &TextEditing, string: &str) -> Option<(usize, usize)> { if editing.highlight_vector == 0 { return None; diff --git a/amethyst_ui/src/pass.rs b/amethyst_ui/src/pass.rs index b9633f9792..538b0d99f1 100644 --- a/amethyst_ui/src/pass.rs +++ b/amethyst_ui/src/pass.rs @@ -39,7 +39,7 @@ use glsl_layout::{vec2, vec4, AsStd140}; use thread_profiler::profile_scope; use crate::{ - glyphs::{UiGlyphs, UiGlyphsResource, UiGlyphsSystem}, + glyphs::{UiGlyphs, UiGlyphsResource}, Selected, TextEditing, UiImage, UiTransform, }; @@ -65,7 +65,8 @@ impl RenderPlugin for RenderUi { builder: &mut DispatcherBuilder, ) -> Result<(), Error> { resources.insert(UiGlyphsResource::default()); - builder.add_system(Box::new(UiGlyphsSystem::::default())); + + builder.add_system(Box::new(crate::glyphs::UiGlyphsSystem::::default())); Ok(()) } diff --git a/book/src/pong-tutorial/pong-tutorial-03.md b/book/src/pong-tutorial/pong-tutorial-03.md index a52be7ea6f..89fdbf2c8e 100644 --- a/book/src/pong-tutorial/pong-tutorial-03.md +++ b/book/src/pong-tutorial/pong-tutorial-03.md @@ -54,7 +54,7 @@ axes we defined. Let's make the following changes to `main.rs`. # extern crate amethyst; # use amethyst::prelude::*; # use amethyst::core::transform::TransformBundle; -# use amethyst::utils::application_root_dir; +# use amethyst_utils::application_root_dir; # use amethyst::window::DisplayConfig; # macro_rules! env { ($x:expr) => ("") } # fn main() -> amethyst::Result<()> { diff --git a/examples/auto_fov/main.rs b/examples/auto_fov/main.rs index 88560469bf..62812ccb3f 100644 --- a/examples/auto_fov/main.rs +++ b/examples/auto_fov/main.rs @@ -42,7 +42,7 @@ const CLEAR_COLOR: ClearColor = ClearColor { fn main() -> Result<(), Error> { amethyst::start_logger(Default::default()); - let app_dir = amethyst::utils::application_dir("examples")?; + let app_dir = amethyst_utils::application_dir("examples")?; let display_config_path = app_dir.join("auto_fov/config/display.ron"); let assets_dir = app_dir.join("auto_fov/assets"); diff --git a/examples/ui_from_code/main.rs b/examples/ui_from_code/main.rs index 8ab6628935..dd038e3112 100644 --- a/examples/ui_from_code/main.rs +++ b/examples/ui_from_code/main.rs @@ -6,7 +6,7 @@ use amethyst::{ ui::AudioUiBundle, Application, GameData, LoggerConfig, SimpleState, StateData, }; -use amethyst_audio::{output::init_output, Source}; +use amethyst_assets::LoaderBundle; use amethyst_core::{dispatcher::DispatcherBuilder, transform::TransformBundle}; use amethyst_input::InputBundle; use amethyst_ui::{RenderUi, UiBundle}; @@ -21,12 +21,13 @@ impl SimpleState for Example { world, resources, .. } = data; + log::debug!("Adding button"); example_utils::build_example_button(world, resources); example_utils::build_ui_image_texture(world, resources); // We init the output because complex button has sounds - init_output(resources); - example_utils::build_complex_button_with_font_and_sound(world, resources); + // init_output(resources); + // example_utils::build_complex_button_with_font_and_sound(world, resources); example_utils::build_draggable(world, resources); example_utils::build_multi_line_label(world, resources); @@ -35,7 +36,14 @@ impl SimpleState for Example { } fn main() -> amethyst::Result<()> { - amethyst::start_logger(LoggerConfig::default()); + { + let mut config = amethyst::LoggerConfig::default(); + config.level_filter = amethyst::LogLevelFilter::Info; + config + .module_levels + .push(("amethyst_ui".to_string(), amethyst::LogLevelFilter::Trace)); + amethyst::start_logger(config); + } let app_root = application_root_dir()?; let display_config_path = app_root.join("examples/ui/config/display.ron"); let assets_dir = app_root.join("examples/ui/assets"); @@ -43,18 +51,19 @@ fn main() -> amethyst::Result<()> { let mut dispatcher = DispatcherBuilder::default(); dispatcher - .add_bundle(TransformBundle::default()) + .add_bundle(LoaderBundle) + .add_bundle(TransformBundle) .add_bundle(InputBundle::default()) .add_bundle(UiBundle::::default()) - .add_bundle(AudioUiBundle::default()) + // .add_bundle(AudioUiBundle) .add_bundle( RenderingBundle::::new() + .with_plugin(RenderUi::default()) .with_plugin( RenderToWindow::from_config_path(display_config_path)?.with_clear(ClearColor { float32: [0.34, 0.36, 0.52, 1.0], }), - ) - .with_plugin(RenderUi::default()), + ), ); let game = Application::new(assets_dir, Example::default(), dispatcher)?; @@ -66,12 +75,11 @@ pub struct TestCpnt; mod example_utils { use amethyst::ecs::{Resources, World}; - use amethyst_assets::{AssetStorage, DefaultLoader, Format, Loader, ProcessingQueue}; - use amethyst_audio::{OggFormat, Source}; - use amethyst_rendy::{types::TextureData, ImageFormat, Texture}; + use amethyst_assets::{DefaultLoader, Format, Loader, ProcessingQueue}; + use amethyst_rendy::{types::TextureData, ImageFormat}; use amethyst_ui::{ - Anchor, Draggable, FontAsset, Interactable, LineMode, TextEditing, TtfFormat, - UiButtonBuilder, UiImage, UiLabelBuilder, UiTransform, + Anchor, Draggable, Interactable, LineMode, TextEditing, UiButtonBuilder, UiImage, + UiLabelBuilder, UiTransform, }; pub fn build_example_button(world: &mut World, resources: &mut Resources) { @@ -89,7 +97,6 @@ mod example_utils { pub fn build_multi_line_label(world: &mut World, resources: &mut Resources) { let font = { - let font_storage = resources.get_mut::>().unwrap(); resources .get::() .unwrap() @@ -109,7 +116,6 @@ mod example_utils { pub fn build_editable_text(world: &mut World, resources: &mut Resources) { let font = { - let font_storage = resources.get_mut::>().unwrap(); resources .get::() .unwrap() @@ -143,7 +149,6 @@ mod example_utils { pub fn build_complex_button_with_font_and_sound(world: &mut World, resources: &mut Resources) { let font = { - let font_storage = resources.get_mut::>().unwrap(); resources .get::() .unwrap() @@ -151,7 +156,6 @@ mod example_utils { }; let hover_sound = { - let sound_storage = resources.get_mut::>().unwrap(); resources .get::() .unwrap() @@ -159,7 +163,6 @@ mod example_utils { }; let confirm_sound = { - let sound_storage = resources.get_mut::>().unwrap(); resources .get::() .unwrap() @@ -212,7 +215,6 @@ mod example_utils { pub fn build_draggable(world: &mut World, resources: &mut Resources) { let font = { - let font_storage = resources.get_mut::>().unwrap(); resources .get::() .unwrap() diff --git a/src/lib.rs b/src/lib.rs index a14b4138f3..fbb47616c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,6 +89,7 @@ pub use amethyst_rendy as renderer; pub use amethyst_tiles as tiles; #[cfg(feature = "ui")] pub use amethyst_ui as ui; +#[cfg(feature = "utils")] pub use amethyst_utils as utils; pub use amethyst_window as window; pub use winit;