add <image> widget
This commit is contained in:
@@ -265,7 +265,7 @@ enum SubcommandPanelModify {
|
|||||||
/// Color in HTML hex format (#rrggbb or #rrggbbaa)
|
/// Color in HTML hex format (#rrggbb or #rrggbbaa)
|
||||||
hex_color: String,
|
hex_color: String,
|
||||||
},
|
},
|
||||||
/// Set the content of a sprite
|
/// Set the content of a <sprite> or <image>. Max size for <sprite> is 256x256.
|
||||||
SetSprite {
|
SetSprite {
|
||||||
/// Absolute path to a svg, gif, png, jpeg or webp image.
|
/// Absolute path to a svg, gif, png, jpeg or webp image.
|
||||||
absolute_path: String,
|
absolute_path: String,
|
||||||
|
|||||||
@@ -160,7 +160,17 @@ _2nd gradient color_
|
|||||||
|
|
||||||
### `<sprite>`
|
### `<sprite>`
|
||||||
|
|
||||||
### Image widget, supports raster and svg vector
|
### Image widget for small images,
|
||||||
|
|
||||||
|
Supported formats: svg, png, jpeg, gif, webp
|
||||||
|
|
||||||
|
Maximum image size: 256x256 pixels
|
||||||
|
|
||||||
|
For large or frequently changing images (e.g. album art), or if borders/rounding is desired, consider the `<image>` tag instead.
|
||||||
|
|
||||||
|
Sprite images are atlassed, making them very efficient to render.
|
||||||
|
|
||||||
|
Adding large sprites permanently increases the atlas size (and thus VRAM usage) for the session. (Atlas space can be re-used, but the atlas won't shrink back.)
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
@@ -180,6 +190,44 @@ _Internal (assets) image path_
|
|||||||
|
|
||||||
_wgui internal image path. Do not use directly unless it's related to the core wgui assets._
|
_wgui internal image path. Do not use directly unless it's related to the core wgui assets._
|
||||||
|
|
||||||
|
## image widget
|
||||||
|
|
||||||
|
### `<image>`
|
||||||
|
|
||||||
|
### Image widget for large images
|
||||||
|
|
||||||
|
Supported formats: svg, png, jpeg, gif, webp
|
||||||
|
|
||||||
|
Maximum image size: Max texture size for the GPU (usually 8K+)
|
||||||
|
|
||||||
|
For small images such as icons, consider using the `<sprite>` tag instead.
|
||||||
|
|
||||||
|
`<image>` requires a single draw call per widget, while `<sprite>` widgets all share a single draw call per panel.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
`src`: **string**
|
||||||
|
|
||||||
|
_External (filesystem) image path. Falls back to Internal (assets) if not found._
|
||||||
|
|
||||||
|
`src_ext`: **string**
|
||||||
|
|
||||||
|
_External (filesystem) image path_
|
||||||
|
|
||||||
|
`src_builtin`: **string**
|
||||||
|
|
||||||
|
_Internal (assets) image path_
|
||||||
|
|
||||||
|
`src_internal`: **string**
|
||||||
|
|
||||||
|
_wgui internal image path. Do not use directly unless it's related to the core wgui assets._
|
||||||
|
|
||||||
|
`round`: **float** (default: 0) | **percent** (0-100%)
|
||||||
|
|
||||||
|
`border`: **float**
|
||||||
|
|
||||||
|
`border_color`: #FFAABB | #FFAABBCC
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Components
|
# Components
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ use crate::{
|
|||||||
event::EventAlterables,
|
event::EventAlterables,
|
||||||
globals::Globals,
|
globals::Globals,
|
||||||
layout::Widget,
|
layout::Widget,
|
||||||
renderer_vk::text::{TextShadow, custom_glyph::CustomGlyph},
|
renderer_vk::text::{
|
||||||
|
custom_glyph::{CustomGlyph, CustomGlyphData},
|
||||||
|
TextShadow,
|
||||||
|
},
|
||||||
stack::{self, ScissorBoundary, ScissorStack, TransformStack},
|
stack::{self, ScissorBoundary, ScissorStack, TransformStack},
|
||||||
widget::{self, ScrollbarInfo, WidgetState},
|
widget::{self, ScrollbarInfo, WidgetState},
|
||||||
};
|
};
|
||||||
@@ -175,6 +178,17 @@ pub struct Rectangle {
|
|||||||
pub round_units: u8,
|
pub round_units: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ImagePrimitive {
|
||||||
|
pub content: CustomGlyphData,
|
||||||
|
pub content_key: usize,
|
||||||
|
|
||||||
|
pub border: f32, // width in pixels
|
||||||
|
pub border_color: Color,
|
||||||
|
|
||||||
|
pub round_units: u8,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct PrimitiveExtent {
|
pub struct PrimitiveExtent {
|
||||||
pub(super) boundary: Boundary,
|
pub(super) boundary: Boundary,
|
||||||
pub(super) transform: Mat4,
|
pub(super) transform: Mat4,
|
||||||
@@ -185,6 +199,7 @@ pub enum RenderPrimitive {
|
|||||||
Rectangle(PrimitiveExtent, Rectangle),
|
Rectangle(PrimitiveExtent, Rectangle),
|
||||||
Text(PrimitiveExtent, Rc<RefCell<Buffer>>, Option<TextShadow>),
|
Text(PrimitiveExtent, Rc<RefCell<Buffer>>, Option<TextShadow>),
|
||||||
Sprite(PrimitiveExtent, Option<CustomGlyph>), //option because we want as_slice
|
Sprite(PrimitiveExtent, Option<CustomGlyph>), //option because we want as_slice
|
||||||
|
Image(PrimitiveExtent, ImagePrimitive),
|
||||||
ScissorSet(ScissorBoundary),
|
ScissorSet(ScissorBoundary),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mod component_checkbox;
|
|||||||
mod component_slider;
|
mod component_slider;
|
||||||
mod style;
|
mod style;
|
||||||
mod widget_div;
|
mod widget_div;
|
||||||
|
mod widget_image;
|
||||||
mod widget_label;
|
mod widget_label;
|
||||||
mod widget_rectangle;
|
mod widget_rectangle;
|
||||||
mod widget_sprite;
|
mod widget_sprite;
|
||||||
@@ -15,8 +16,8 @@ use crate::{
|
|||||||
layout::{Layout, LayoutParams, LayoutState, Widget, WidgetID, WidgetMap, WidgetPair},
|
layout::{Layout, LayoutParams, LayoutState, Widget, WidgetID, WidgetMap, WidgetPair},
|
||||||
parser::{
|
parser::{
|
||||||
component_button::parse_component_button, component_checkbox::parse_component_checkbox,
|
component_button::parse_component_button, component_checkbox::parse_component_checkbox,
|
||||||
component_slider::parse_component_slider, widget_div::parse_widget_div, widget_label::parse_widget_label,
|
component_slider::parse_component_slider, widget_div::parse_widget_div, widget_image::parse_widget_image,
|
||||||
widget_rectangle::parse_widget_rectangle, widget_sprite::parse_widget_sprite,
|
widget_label::parse_widget_label, widget_rectangle::parse_widget_rectangle, widget_sprite::parse_widget_sprite,
|
||||||
},
|
},
|
||||||
widget::ConstructEssentials,
|
widget::ConstructEssentials,
|
||||||
};
|
};
|
||||||
@@ -898,6 +899,9 @@ fn parse_child<'a>(
|
|||||||
"sprite" => {
|
"sprite" => {
|
||||||
new_widget_id = Some(parse_widget_sprite(file, ctx, child_node, parent_id, &attribs)?);
|
new_widget_id = Some(parse_widget_sprite(file, ctx, child_node, parent_id, &attribs)?);
|
||||||
}
|
}
|
||||||
|
"image" => {
|
||||||
|
new_widget_id = Some(parse_widget_image(file, ctx, child_node, parent_id, &attribs)?);
|
||||||
|
}
|
||||||
"Button" => {
|
"Button" => {
|
||||||
new_widget_id = Some(parse_component_button(file, ctx, child_node, parent_id, &attribs)?);
|
new_widget_id = Some(parse_component_button(file, ctx, child_node, parent_id, &attribs)?);
|
||||||
}
|
}
|
||||||
|
|||||||
78
wgui/src/parser/widget_image.rs
Normal file
78
wgui/src/parser/widget_image.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
use crate::{
|
||||||
|
assets::AssetPath,
|
||||||
|
layout::WidgetID,
|
||||||
|
parser::{
|
||||||
|
parse_children, parse_widget_universal, print_invalid_attrib,
|
||||||
|
style::{parse_color, parse_round, parse_style},
|
||||||
|
AttribPair, ParserContext, ParserFile,
|
||||||
|
},
|
||||||
|
renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData},
|
||||||
|
widget::image::{WidgetImage, WidgetImageParams},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn parse_widget_image<'a>(
|
||||||
|
file: &ParserFile,
|
||||||
|
ctx: &mut ParserContext,
|
||||||
|
node: roxmltree::Node<'a, 'a>,
|
||||||
|
parent_id: WidgetID,
|
||||||
|
attribs: &[AttribPair],
|
||||||
|
) -> anyhow::Result<WidgetID> {
|
||||||
|
let mut params = WidgetImageParams::default();
|
||||||
|
let style = parse_style(attribs);
|
||||||
|
let mut glyph = None;
|
||||||
|
|
||||||
|
for pair in attribs {
|
||||||
|
let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
|
||||||
|
match key {
|
||||||
|
"src" | "src_ext" | "src_builtin" | "src_internal" => {
|
||||||
|
let asset_path = match key {
|
||||||
|
"src" => AssetPath::FileOrBuiltIn(value),
|
||||||
|
"src_ext" => AssetPath::File(value),
|
||||||
|
"src_builtin" => AssetPath::BuiltIn(value),
|
||||||
|
"src_internal" => AssetPath::WguiInternal(value),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !value.is_empty() {
|
||||||
|
glyph = match CustomGlyphContent::from_assets(&mut ctx.layout.state.globals, asset_path) {
|
||||||
|
Ok(glyph) => Some(glyph),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("failed to load {value}: {e}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"round" => {
|
||||||
|
parse_round(
|
||||||
|
value,
|
||||||
|
&mut params.round,
|
||||||
|
ctx.doc_params.globals.get().defaults.rounding_mult,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
"border" => {
|
||||||
|
params.border = value.parse().unwrap_or_else(|_| {
|
||||||
|
print_invalid_attrib(key, value);
|
||||||
|
0.0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
"border_color" => {
|
||||||
|
parse_color(value, &mut params.border_color);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(glyph) = glyph {
|
||||||
|
params.glyph_data = Some(CustomGlyphData::new(glyph));
|
||||||
|
} else {
|
||||||
|
log::warn!("No source for image node!");
|
||||||
|
}
|
||||||
|
|
||||||
|
let (widget, _) = ctx.layout.add_child(parent_id, WidgetImage::create(params), style)?;
|
||||||
|
|
||||||
|
parse_widget_universal(ctx, &widget, attribs);
|
||||||
|
parse_children(file, ctx, node, widget.id)?;
|
||||||
|
|
||||||
|
Ok(widget.id)
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
assets::AssetPath,
|
assets::AssetPath,
|
||||||
layout::WidgetID,
|
layout::WidgetID,
|
||||||
parser::{AttribPair, ParserContext, ParserFile, parse_children, parse_widget_universal, style::parse_style},
|
parser::{parse_children, parse_widget_universal, style::parse_style, AttribPair, ParserContext, ParserFile},
|
||||||
renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData},
|
renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData},
|
||||||
widget::sprite::{WidgetSprite, WidgetSpriteParams},
|
widget::sprite::{WidgetSprite, WidgetSpriteParams},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,21 +2,22 @@ use std::{cell::RefCell, rc::Rc, sync::Arc};
|
|||||||
|
|
||||||
use cosmic_text::Buffer;
|
use cosmic_text::Buffer;
|
||||||
use glam::{Mat4, Vec2, Vec3};
|
use glam::{Mat4, Vec2, Vec3};
|
||||||
use slotmap::{SlotMap, new_key_type};
|
use slotmap::{new_key_type, SlotMap};
|
||||||
use vulkano::pipeline::graphics::viewport;
|
use vulkano::pipeline::graphics::viewport;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
drawing::{self},
|
drawing::{self},
|
||||||
font_config,
|
font_config,
|
||||||
gfx::{WGfx, cmd::GfxCommandBuffer},
|
gfx::{cmd::GfxCommandBuffer, WGfx},
|
||||||
|
renderer_vk::image::{ImagePipeline, ImageRenderer},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
rect::{RectPipeline, RectRenderer},
|
rect::{RectPipeline, RectRenderer},
|
||||||
text::{
|
text::{
|
||||||
DEFAULT_METRICS, SWASH_CACHE, TextArea, TextBounds,
|
|
||||||
text_atlas::{TextAtlas, TextPipeline},
|
text_atlas::{TextAtlas, TextPipeline},
|
||||||
text_renderer::TextRenderer,
|
text_renderer::TextRenderer,
|
||||||
|
TextArea, TextBounds, DEFAULT_METRICS, SWASH_CACHE,
|
||||||
},
|
},
|
||||||
viewport::Viewport,
|
viewport::Viewport,
|
||||||
};
|
};
|
||||||
@@ -26,6 +27,7 @@ struct RendererPass<'a> {
|
|||||||
text_areas: Vec<TextArea<'a>>,
|
text_areas: Vec<TextArea<'a>>,
|
||||||
text_renderer: TextRenderer,
|
text_renderer: TextRenderer,
|
||||||
rect_renderer: RectRenderer,
|
rect_renderer: RectRenderer,
|
||||||
|
image_renderer: ImageRenderer,
|
||||||
scissor: Option<drawing::Boundary>,
|
scissor: Option<drawing::Boundary>,
|
||||||
pixel_scale: f32,
|
pixel_scale: f32,
|
||||||
}
|
}
|
||||||
@@ -34,16 +36,19 @@ impl RendererPass<'_> {
|
|||||||
fn new(
|
fn new(
|
||||||
text_atlas: &mut TextAtlas,
|
text_atlas: &mut TextAtlas,
|
||||||
rect_pipeline: RectPipeline,
|
rect_pipeline: RectPipeline,
|
||||||
|
image_pipeline: ImagePipeline,
|
||||||
scissor: Option<drawing::Boundary>,
|
scissor: Option<drawing::Boundary>,
|
||||||
pixel_scale: f32,
|
pixel_scale: f32,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
let text_renderer = TextRenderer::new(text_atlas)?;
|
let text_renderer = TextRenderer::new(text_atlas)?;
|
||||||
let rect_renderer = RectRenderer::new(rect_pipeline)?;
|
let rect_renderer = RectRenderer::new(rect_pipeline)?;
|
||||||
|
let image_renderer = ImageRenderer::new(image_pipeline)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
submitted: false,
|
submitted: false,
|
||||||
text_renderer,
|
text_renderer,
|
||||||
rect_renderer,
|
rect_renderer,
|
||||||
|
image_renderer,
|
||||||
text_areas: Vec::new(),
|
text_areas: Vec::new(),
|
||||||
scissor,
|
scissor,
|
||||||
pixel_scale,
|
pixel_scale,
|
||||||
@@ -90,6 +95,7 @@ impl RendererPass<'_> {
|
|||||||
|
|
||||||
self.submitted = true;
|
self.submitted = true;
|
||||||
self.rect_renderer.render(gfx, viewport, &vk_scissor, cmd_buf)?;
|
self.rect_renderer.render(gfx, viewport, &vk_scissor, cmd_buf)?;
|
||||||
|
self.image_renderer.render(gfx, viewport, &vk_scissor, cmd_buf)?;
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut font_system = font_system.system.lock();
|
let mut font_system = font_system.system.lock();
|
||||||
@@ -119,18 +125,21 @@ pub struct SharedContext {
|
|||||||
atlas_map: SlotMap<SharedContextKey, SharedAtlas>,
|
atlas_map: SlotMap<SharedContextKey, SharedAtlas>,
|
||||||
rect_pipeline: RectPipeline,
|
rect_pipeline: RectPipeline,
|
||||||
text_pipeline: TextPipeline,
|
text_pipeline: TextPipeline,
|
||||||
|
image_pipeline: ImagePipeline,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedContext {
|
impl SharedContext {
|
||||||
pub fn new(gfx: Arc<WGfx>) -> anyhow::Result<Self> {
|
pub fn new(gfx: Arc<WGfx>) -> anyhow::Result<Self> {
|
||||||
let rect_pipeline = RectPipeline::new(gfx.clone(), gfx.surface_format)?;
|
let rect_pipeline = RectPipeline::new(gfx.clone(), gfx.surface_format)?;
|
||||||
let text_pipeline = TextPipeline::new(gfx.clone(), gfx.surface_format)?;
|
let text_pipeline = TextPipeline::new(gfx.clone(), gfx.surface_format)?;
|
||||||
|
let image_pipeline = ImagePipeline::new(gfx.clone(), gfx.surface_format)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
gfx,
|
gfx,
|
||||||
atlas_map: SlotMap::with_key(),
|
atlas_map: SlotMap::with_key(),
|
||||||
rect_pipeline,
|
rect_pipeline,
|
||||||
text_pipeline,
|
text_pipeline,
|
||||||
|
image_pipeline,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,6 +246,7 @@ impl Context {
|
|||||||
passes.push(RendererPass::new(
|
passes.push(RendererPass::new(
|
||||||
&mut atlas.text_atlas,
|
&mut atlas.text_atlas,
|
||||||
shared.rect_pipeline.clone(),
|
shared.rect_pipeline.clone(),
|
||||||
|
shared.image_pipeline.clone(),
|
||||||
next_scissor,
|
next_scissor,
|
||||||
self.pixel_scale,
|
self.pixel_scale,
|
||||||
)?);
|
)?);
|
||||||
@@ -293,6 +303,11 @@ impl Context {
|
|||||||
transform: extent.transform,
|
transform: extent.transform,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
drawing::RenderPrimitive::Image(extent, image) => {
|
||||||
|
pass
|
||||||
|
.image_renderer
|
||||||
|
.add_image(extent.boundary, image.clone(), &extent.transform);
|
||||||
|
}
|
||||||
drawing::RenderPrimitive::ScissorSet(boundary) => {
|
drawing::RenderPrimitive::ScissorSet(boundary) => {
|
||||||
next_scissor = Some(boundary.0);
|
next_scissor = Some(boundary.0);
|
||||||
needs_new_pass = true;
|
needs_new_pass = true;
|
||||||
|
|||||||
244
wgui/src/renderer_vk/image.rs
Normal file
244
wgui/src/renderer_vk/image.rs
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
|
use cosmic_text::SubpixelBin;
|
||||||
|
use glam::Mat4;
|
||||||
|
use smallvec::smallvec;
|
||||||
|
use vulkano::{
|
||||||
|
buffer::{BufferContents, BufferUsage, Subbuffer},
|
||||||
|
command_buffer::CommandBufferUsage,
|
||||||
|
format::Format,
|
||||||
|
image::view::ImageView,
|
||||||
|
pipeline::graphics::{self, vertex_input::Vertex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
drawing::{Boundary, ImagePrimitive},
|
||||||
|
gfx::{
|
||||||
|
cmd::GfxCommandBuffer,
|
||||||
|
pass::WGfxPass,
|
||||||
|
pipeline::{WGfxPipeline, WPipelineCreateInfo},
|
||||||
|
WGfx, BLEND_ALPHA,
|
||||||
|
},
|
||||||
|
renderer_vk::{
|
||||||
|
model_buffer::ModelBuffer,
|
||||||
|
text::custom_glyph::{CustomGlyphData, RasterizeCustomGlyphRequest, RasterizedCustomGlyph},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::viewport::Viewport;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(BufferContents, Vertex, Copy, Clone, Debug)]
|
||||||
|
pub struct ImageVertex {
|
||||||
|
#[format(R32_UINT)]
|
||||||
|
pub in_model_idx: u32,
|
||||||
|
#[format(R32_UINT)]
|
||||||
|
pub in_rect_dim: [u16; 2],
|
||||||
|
#[format(R32_UINT)]
|
||||||
|
pub in_border_color: u32,
|
||||||
|
#[format(R32_UINT)]
|
||||||
|
pub round_border: [u8; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cloneable pipeline & shaders to be shared between `RectRenderer` instances.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ImagePipeline {
|
||||||
|
gfx: Arc<WGfx>,
|
||||||
|
pub(super) inner: Arc<WGfxPipeline<ImageVertex>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImagePipeline {
|
||||||
|
pub fn new(gfx: Arc<WGfx>, format: Format) -> anyhow::Result<Self> {
|
||||||
|
let vert = vert_image::load(gfx.device.clone())?;
|
||||||
|
let frag = frag_image::load(gfx.device.clone())?;
|
||||||
|
|
||||||
|
let pipeline = gfx.create_pipeline::<ImageVertex>(
|
||||||
|
&vert,
|
||||||
|
&frag,
|
||||||
|
WPipelineCreateInfo::new(format)
|
||||||
|
.use_blend(BLEND_ALPHA)
|
||||||
|
.use_instanced()
|
||||||
|
.use_updatable_descriptors(smallvec![2]),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Self { gfx, inner: pipeline })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ImageVertexWithContent {
|
||||||
|
vert: ImageVertex,
|
||||||
|
content: CustomGlyphData,
|
||||||
|
content_key: usize, // identifies an image tag.
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CachedPass {
|
||||||
|
content_id: usize,
|
||||||
|
vert_buffer: Subbuffer<[ImageVertex]>,
|
||||||
|
inner: WGfxPass<ImageVertex>,
|
||||||
|
res: [u32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ImageRenderer {
|
||||||
|
pipeline: ImagePipeline,
|
||||||
|
image_verts: Vec<ImageVertexWithContent>,
|
||||||
|
model_buffer: ModelBuffer,
|
||||||
|
cached_passes: HashMap<usize, CachedPass>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageRenderer {
|
||||||
|
pub fn new(pipeline: ImagePipeline) -> anyhow::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
model_buffer: ModelBuffer::new(&pipeline.gfx)?,
|
||||||
|
pipeline,
|
||||||
|
image_verts: vec![],
|
||||||
|
cached_passes: HashMap::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn begin(&mut self) {
|
||||||
|
self.image_verts.clear();
|
||||||
|
self.model_buffer.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_image(&mut self, boundary: Boundary, image: ImagePrimitive, transform: &Mat4) {
|
||||||
|
let in_model_idx = self
|
||||||
|
.model_buffer
|
||||||
|
.register_pos_size(&boundary.pos, &boundary.size, transform);
|
||||||
|
|
||||||
|
self.image_verts.push(ImageVertexWithContent {
|
||||||
|
vert: ImageVertex {
|
||||||
|
in_model_idx,
|
||||||
|
in_rect_dim: [boundary.size.x as u16, boundary.size.y as u16],
|
||||||
|
in_border_color: cosmic_text::Color::from(image.border_color).0,
|
||||||
|
round_border: [
|
||||||
|
image.round_units,
|
||||||
|
(image.border) as u8,
|
||||||
|
0, // unused
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
content: image.content,
|
||||||
|
content_key: image.content_key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upload_image(
|
||||||
|
gfx: Arc<WGfx>,
|
||||||
|
res: [u32; 2],
|
||||||
|
img: &ImageVertexWithContent,
|
||||||
|
) -> anyhow::Result<Option<Arc<ImageView>>> {
|
||||||
|
let raster = match RasterizedCustomGlyph::try_from(&RasterizeCustomGlyphRequest {
|
||||||
|
data: img.content.clone(),
|
||||||
|
width: res[0] as _,
|
||||||
|
height: res[1] as _,
|
||||||
|
x_bin: SubpixelBin::Zero,
|
||||||
|
y_bin: SubpixelBin::Zero,
|
||||||
|
scale: 1.0, // unused
|
||||||
|
}) {
|
||||||
|
Some(x) => x,
|
||||||
|
None => {
|
||||||
|
log::error!("Unable to rasterize custom image");
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut cmd_buf = gfx.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
||||||
|
|
||||||
|
let image = cmd_buf.upload_image(
|
||||||
|
raster.width as _,
|
||||||
|
raster.height as _,
|
||||||
|
Format::R8G8B8A8_UNORM,
|
||||||
|
&raster.data,
|
||||||
|
)?;
|
||||||
|
let image_view = ImageView::new_default(image)?;
|
||||||
|
|
||||||
|
cmd_buf.build_and_execute_now()?;
|
||||||
|
|
||||||
|
Ok(Some(image_view))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(
|
||||||
|
&mut self,
|
||||||
|
gfx: &Arc<WGfx>,
|
||||||
|
viewport: &mut Viewport,
|
||||||
|
vk_scissor: &graphics::viewport::Scissor,
|
||||||
|
cmd_buf: &mut GfxCommandBuffer,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let res = viewport.resolution();
|
||||||
|
self.model_buffer.upload(gfx)?;
|
||||||
|
|
||||||
|
for img in self.image_verts.iter() {
|
||||||
|
let pass = match self.cached_passes.get_mut(&img.content_key) {
|
||||||
|
Some(x) => {
|
||||||
|
if x.content_id != img.content.id || x.res != res {
|
||||||
|
// image changed
|
||||||
|
let Some(image_view) = Self::upload_image(self.pipeline.gfx.clone(), res, img)? else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
x.inner
|
||||||
|
.update_sampler(2, image_view, self.pipeline.gfx.texture_filter)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
x
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let vert_buffer = self.pipeline.gfx.empty_buffer(
|
||||||
|
BufferUsage::VERTEX_BUFFER | BufferUsage::TRANSFER_DST,
|
||||||
|
(std::mem::size_of::<ImageVertex>()) as _,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let Some(image_view) = Self::upload_image(self.pipeline.gfx.clone(), res, img)? else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let set0 = viewport.get_image_descriptor(&self.pipeline);
|
||||||
|
let set1 = self.model_buffer.get_image_descriptor(&self.pipeline);
|
||||||
|
let set2 = self
|
||||||
|
.pipeline
|
||||||
|
.inner
|
||||||
|
.uniform_sampler(2, image_view, self.pipeline.gfx.texture_filter)?;
|
||||||
|
|
||||||
|
let pass = self.pipeline.inner.create_pass(
|
||||||
|
[res[0] as _, res[1] as _],
|
||||||
|
vert_buffer.clone(),
|
||||||
|
0..4,
|
||||||
|
0..self.image_verts.len() as _,
|
||||||
|
vec![set0, set1, set2],
|
||||||
|
vk_scissor,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.cached_passes.insert(
|
||||||
|
img.content_key,
|
||||||
|
CachedPass {
|
||||||
|
content_id: img.content.id,
|
||||||
|
vert_buffer,
|
||||||
|
inner: pass,
|
||||||
|
res,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.cached_passes.get_mut(&img.content_key).unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pass.vert_buffer.write()?[0..1].clone_from_slice(&[img.vert]);
|
||||||
|
|
||||||
|
cmd_buf.run_ref(&pass.inner)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod vert_image {
|
||||||
|
vulkano_shaders::shader! {
|
||||||
|
ty: "vertex",
|
||||||
|
path: "src/renderer_vk/shaders/image.vert",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod frag_image {
|
||||||
|
vulkano_shaders::shader! {
|
||||||
|
ty: "fragment",
|
||||||
|
path: "src/renderer_vk/shaders/image.frag",
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
pub mod context;
|
pub mod context;
|
||||||
|
pub mod image;
|
||||||
pub mod model_buffer;
|
pub mod model_buffer;
|
||||||
pub mod rect;
|
pub mod rect;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use vulkano::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
gfx,
|
gfx,
|
||||||
renderer_vk::{rect::RectPipeline, text::text_atlas::TextPipeline},
|
renderer_vk::{image::ImagePipeline, rect::RectPipeline, text::text_atlas::TextPipeline},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ModelBuffer {
|
pub struct ModelBuffer {
|
||||||
@@ -19,6 +19,8 @@ pub struct ModelBuffer {
|
|||||||
buffer_capacity_f32: u32,
|
buffer_capacity_f32: u32,
|
||||||
|
|
||||||
rect_descriptor: Option<Arc<DescriptorSet>>,
|
rect_descriptor: Option<Arc<DescriptorSet>>,
|
||||||
|
text_descriptor: Option<Arc<DescriptorSet>>,
|
||||||
|
image_descriptor: Option<Arc<DescriptorSet>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModelBuffer {
|
impl ModelBuffer {
|
||||||
@@ -40,6 +42,8 @@ impl ModelBuffer {
|
|||||||
buffer,
|
buffer,
|
||||||
buffer_capacity_f32: INITIAL_CAPACITY_F32,
|
buffer_capacity_f32: INITIAL_CAPACITY_F32,
|
||||||
rect_descriptor: None,
|
rect_descriptor: None,
|
||||||
|
text_descriptor: None,
|
||||||
|
image_descriptor: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,8 +113,15 @@ impl ModelBuffer {
|
|||||||
|
|
||||||
pub fn get_text_descriptor(&mut self, pipeline: &TextPipeline) -> Arc<DescriptorSet> {
|
pub fn get_text_descriptor(&mut self, pipeline: &TextPipeline) -> Arc<DescriptorSet> {
|
||||||
self
|
self
|
||||||
.rect_descriptor
|
.text_descriptor
|
||||||
.get_or_insert_with(|| pipeline.inner.buffer(3, self.buffer.clone()).unwrap())
|
.get_or_insert_with(|| pipeline.inner.buffer(3, self.buffer.clone()).unwrap())
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_image_descriptor(&mut self, pipeline: &ImagePipeline) -> Arc<DescriptorSet> {
|
||||||
|
self
|
||||||
|
.image_descriptor
|
||||||
|
.get_or_insert_with(|| pipeline.inner.buffer(1, self.buffer.clone()).unwrap())
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ use vulkano::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
drawing::{Boundary, Rectangle},
|
drawing::{Boundary, Rectangle},
|
||||||
gfx::{
|
gfx::{
|
||||||
BLEND_ALPHA, WGfx,
|
|
||||||
cmd::GfxCommandBuffer,
|
cmd::GfxCommandBuffer,
|
||||||
pass::WGfxPass,
|
pass::WGfxPass,
|
||||||
pipeline::{WGfxPipeline, WPipelineCreateInfo},
|
pipeline::{WGfxPipeline, WPipelineCreateInfo},
|
||||||
|
WGfx, BLEND_ALPHA,
|
||||||
},
|
},
|
||||||
renderer_vk::model_buffer::ModelBuffer,
|
renderer_vk::model_buffer::ModelBuffer,
|
||||||
};
|
};
|
||||||
|
|||||||
55
wgui/src/renderer_vk/shaders/image.frag
Normal file
55
wgui/src/renderer_vk/shaders/image.frag
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
#version 450
|
||||||
|
#extension GL_GOOGLE_include_directive : enable
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 in_uv;
|
||||||
|
layout(location = 1) in vec4 in_border_color;
|
||||||
|
layout(location = 2) in float in_border_size; // in units
|
||||||
|
layout(location = 3) in float in_radius; // in units
|
||||||
|
layout(location = 4) in vec2 in_rect_size;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
#define UNIFORM_PARAMS_SET 0
|
||||||
|
#include "uniform.glsl"
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D image;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float rect_aspect = in_rect_size.x / in_rect_size.y;
|
||||||
|
|
||||||
|
vec2 center = in_rect_size / 2.0;
|
||||||
|
vec2 coords = in_uv * in_rect_size;
|
||||||
|
|
||||||
|
|
||||||
|
float radius = in_radius;
|
||||||
|
|
||||||
|
vec2 sdf_rect_dim = center - vec2(radius);
|
||||||
|
float sdf = length(max(abs(coords - center), sdf_rect_dim) - sdf_rect_dim) -
|
||||||
|
in_radius;
|
||||||
|
|
||||||
|
vec4 color = texture(image, in_uv);
|
||||||
|
|
||||||
|
float pixel_size = 1.0 / uniforms.pixel_scale;
|
||||||
|
|
||||||
|
if (in_border_size < in_radius) {
|
||||||
|
// rounded border
|
||||||
|
float f = in_border_size > 0.0 ? smoothstep(in_border_size + pixel_size,
|
||||||
|
in_border_size, -sdf) *
|
||||||
|
in_border_color.a
|
||||||
|
: 0.0;
|
||||||
|
out_color = mix(color, in_border_color, f);
|
||||||
|
} else {
|
||||||
|
// square border
|
||||||
|
vec2 a = abs(coords - center);
|
||||||
|
float aa = center.x - in_border_size;
|
||||||
|
float bb = center.y - in_border_size;
|
||||||
|
out_color = (a.x > aa || a.y > bb) ? in_border_color : color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_radius > 0.0) {
|
||||||
|
// rounding cutout alpha
|
||||||
|
out_color.a *= 1.0 - smoothstep(-pixel_size, 0.0, sdf);
|
||||||
|
}
|
||||||
|
}
|
||||||
52
wgui/src/renderer_vk/shaders/image.vert
Normal file
52
wgui/src/renderer_vk/shaders/image.vert
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#version 450
|
||||||
|
#extension GL_GOOGLE_include_directive : enable
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout(location = 0) in uint in_model_idx;
|
||||||
|
layout(location = 1) in uint in_rect_dim;
|
||||||
|
layout(location = 2) in uint in_border_color;
|
||||||
|
layout(location = 3) in uint round_border;
|
||||||
|
|
||||||
|
layout(location = 0) out vec2 out_uv;
|
||||||
|
layout(location = 1) out vec4 out_border_color;
|
||||||
|
layout(location = 2) out float out_border_size;
|
||||||
|
layout(location = 3) out float out_radius;
|
||||||
|
layout(location = 4) out vec2 out_rect_size;
|
||||||
|
|
||||||
|
#define UNIFORM_PARAMS_SET 0
|
||||||
|
#define MODEL_BUFFER_SET 1
|
||||||
|
|
||||||
|
#include "model_buffer.glsl"
|
||||||
|
#include "uniform.glsl"
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
uint v = uint(gl_VertexIndex); // 0-3
|
||||||
|
uint rect_width = in_rect_dim & 0xffffu;
|
||||||
|
uint rect_height = (in_rect_dim & 0xffff0000u) >> 16u;
|
||||||
|
vec2 rect_size = vec2(float(rect_width), float(rect_height));
|
||||||
|
float rect_aspect = rect_size.x / rect_size.y;
|
||||||
|
|
||||||
|
// 0.0 - 1.0 normalized
|
||||||
|
uvec2 corner_pos_u = uvec2(v & 1u, (v >> 1u) & 1u);
|
||||||
|
vec2 corner_pos = vec2(corner_pos_u);
|
||||||
|
out_uv = corner_pos;
|
||||||
|
|
||||||
|
mat4 model_matrix = model_buffer.models[in_model_idx];
|
||||||
|
|
||||||
|
out_rect_size = rect_size;
|
||||||
|
|
||||||
|
gl_Position = uniforms.projection * model_matrix * vec4(corner_pos, 0.0, 1.0);
|
||||||
|
|
||||||
|
out_border_color =
|
||||||
|
vec4(float((in_border_color & 0x00ff0000u) >> 16u) / 255.0,
|
||||||
|
float((in_border_color & 0x0000ff00u) >> 8u) / 255.0,
|
||||||
|
float(in_border_color & 0x000000ffu) / 255.0,
|
||||||
|
float((in_border_color & 0xff000000u) >> 24u) / 255.0);
|
||||||
|
|
||||||
|
float radius = float(round_border & 0xffu);
|
||||||
|
out_radius = radius;
|
||||||
|
|
||||||
|
float border_size = float((round_border & 0xff00u) >> 8);
|
||||||
|
out_border_size = border_size;
|
||||||
|
}
|
||||||
@@ -53,8 +53,8 @@ impl CustomGlyphContent {
|
|||||||
/// Clone and reuse this to avoid atlasing the same content twice.
|
/// Clone and reuse this to avoid atlasing the same content twice.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CustomGlyphData {
|
pub struct CustomGlyphData {
|
||||||
pub(super) id: usize,
|
pub(crate) id: usize,
|
||||||
pub(super) content: Arc<CustomGlyphContent>,
|
pub(crate) content: Arc<CustomGlyphContent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CustomGlyphData {
|
impl CustomGlyphData {
|
||||||
@@ -157,7 +157,7 @@ pub struct RasterizedCustomGlyph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RasterizedCustomGlyph {
|
impl RasterizedCustomGlyph {
|
||||||
pub(super) fn try_from(input: &RasterizeCustomGlyphRequest) -> Option<Self> {
|
pub(crate) fn try_from(input: &RasterizeCustomGlyphRequest) -> Option<Self> {
|
||||||
match input.data.content.as_ref() {
|
match input.data.content.as_ref() {
|
||||||
CustomGlyphContent::Svg(tree) => rasterize_svg(tree, input),
|
CustomGlyphContent::Svg(tree) => rasterize_svg(tree, input),
|
||||||
CustomGlyphContent::Image(data) => Some(rasterize_image(data)),
|
CustomGlyphContent::Image(data) => Some(rasterize_image(data)),
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ use vulkano::{
|
|||||||
descriptor_set::DescriptorSet,
|
descriptor_set::DescriptorSet,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{gfx::WGfx, renderer_vk::util::WMat4};
|
use crate::{
|
||||||
|
gfx::WGfx,
|
||||||
|
renderer_vk::{image::ImagePipeline, util::WMat4},
|
||||||
|
};
|
||||||
|
|
||||||
use super::{rect::RectPipeline, text::text_atlas::TextPipeline};
|
use super::{rect::RectPipeline, text::text_atlas::TextPipeline};
|
||||||
|
|
||||||
@@ -16,6 +19,7 @@ pub struct Viewport {
|
|||||||
params_buffer: Subbuffer<[Params]>,
|
params_buffer: Subbuffer<[Params]>,
|
||||||
text_descriptor: Option<Arc<DescriptorSet>>,
|
text_descriptor: Option<Arc<DescriptorSet>>,
|
||||||
rect_descriptor: Option<Arc<DescriptorSet>>,
|
rect_descriptor: Option<Arc<DescriptorSet>>,
|
||||||
|
image_descriptor: Option<Arc<DescriptorSet>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Viewport {
|
impl Viewport {
|
||||||
@@ -36,6 +40,7 @@ impl Viewport {
|
|||||||
params_buffer,
|
params_buffer,
|
||||||
text_descriptor: None,
|
text_descriptor: None,
|
||||||
rect_descriptor: None,
|
rect_descriptor: None,
|
||||||
|
image_descriptor: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +62,15 @@ impl Viewport {
|
|||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_image_descriptor(&mut self, pipeline: &ImagePipeline) -> Arc<DescriptorSet> {
|
||||||
|
self
|
||||||
|
.image_descriptor
|
||||||
|
.get_or_insert_with(|| {
|
||||||
|
pipeline.inner.buffer(0, self.params_buffer.clone()).unwrap() // safe unwrap
|
||||||
|
})
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Updates the `Viewport` with the given `resolution` and `projection`.
|
/// Updates the `Viewport` with the given `resolution` and `projection`.
|
||||||
pub fn update(&mut self, resolution: [u32; 2], projection: &glam::Mat4, pixel_scale: f32) -> anyhow::Result<()> {
|
pub fn update(&mut self, resolution: [u32; 2], projection: &glam::Mat4, pixel_scale: f32) -> anyhow::Result<()> {
|
||||||
if self.params.screen_resolution == resolution
|
if self.params.screen_resolution == resolution
|
||||||
|
|||||||
113
wgui/src/widget/image.rs
Normal file
113
wgui/src/widget/image.rs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use slotmap::Key;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
drawing::{self, ImagePrimitive, PrimitiveExtent},
|
||||||
|
event::CallbackDataCommon,
|
||||||
|
globals::Globals,
|
||||||
|
layout::WidgetID,
|
||||||
|
renderer_vk::text::custom_glyph::CustomGlyphData,
|
||||||
|
widget::{util::WLength, WidgetStateFlags},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{WidgetObj, WidgetState};
|
||||||
|
|
||||||
|
static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct WidgetImageParams {
|
||||||
|
pub glyph_data: Option<CustomGlyphData>,
|
||||||
|
|
||||||
|
pub border: f32,
|
||||||
|
pub border_color: drawing::Color,
|
||||||
|
|
||||||
|
pub round: WLength,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct WidgetImage {
|
||||||
|
params: WidgetImageParams,
|
||||||
|
id: WidgetID,
|
||||||
|
content_key: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetImage {
|
||||||
|
pub fn create(params: WidgetImageParams) -> WidgetState {
|
||||||
|
WidgetState::new(
|
||||||
|
WidgetStateFlags::default(),
|
||||||
|
Box::new(Self {
|
||||||
|
params,
|
||||||
|
id: WidgetID::null(),
|
||||||
|
content_key: AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_content(&mut self, common: &mut CallbackDataCommon, content: Option<CustomGlyphData>) {
|
||||||
|
if self.params.glyph_data == content {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.params.glyph_data = content;
|
||||||
|
common.mark_widget_dirty(self.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_content(&self) -> Option<CustomGlyphData> {
|
||||||
|
self.params.glyph_data.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetObj for WidgetImage {
|
||||||
|
fn draw(&mut self, state: &mut super::DrawState, _params: &super::DrawParams) {
|
||||||
|
let boundary = drawing::Boundary::construct_relative(state.transform_stack);
|
||||||
|
|
||||||
|
let Some(content) = self.params.glyph_data.clone() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let round_units = match self.params.round {
|
||||||
|
WLength::Units(units) => units as u8,
|
||||||
|
WLength::Percent(percent) => (f32::min(boundary.size.x, boundary.size.y) * percent / 2.0) as u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
state.primitives.push(drawing::RenderPrimitive::Image(
|
||||||
|
PrimitiveExtent {
|
||||||
|
boundary,
|
||||||
|
transform: state.transform_stack.get().transform,
|
||||||
|
},
|
||||||
|
ImagePrimitive {
|
||||||
|
content,
|
||||||
|
content_key: self.content_key,
|
||||||
|
border: self.params.border,
|
||||||
|
border_color: self.params.border_color,
|
||||||
|
round_units,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn measure(
|
||||||
|
&mut self,
|
||||||
|
_globals: &Globals,
|
||||||
|
_known_dimensions: taffy::Size<Option<f32>>,
|
||||||
|
_available_space: taffy::Size<taffy::AvailableSpace>,
|
||||||
|
) -> taffy::Size<f32> {
|
||||||
|
taffy::Size::ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_id(&self) -> WidgetID {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_id(&mut self, id: WidgetID) {
|
||||||
|
self.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_type(&self) -> super::WidgetType {
|
||||||
|
super::WidgetType::Sprite
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_print(&self) -> String {
|
||||||
|
String::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub mod div;
|
pub mod div;
|
||||||
|
pub mod image;
|
||||||
pub mod label;
|
pub mod label;
|
||||||
pub mod rectangle;
|
pub mod rectangle;
|
||||||
pub mod sprite;
|
pub mod sprite;
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ impl WidgetSprite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_content(&mut self, common: &mut CallbackDataCommon, content: Option<CustomGlyphData>) {
|
pub fn set_content(&mut self, common: &mut CallbackDataCommon, content: Option<CustomGlyphData>) {
|
||||||
|
if self.params.glyph_data == content {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.params.glyph_data = content;
|
self.params.glyph_data = content;
|
||||||
common.mark_widget_dirty(self.id);
|
common.mark_widget_dirty(self.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ use wgui::{
|
|||||||
parser::{Fetchable, parse_color_hex},
|
parser::{Fetchable, parse_color_hex},
|
||||||
renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData},
|
renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData},
|
||||||
taffy,
|
taffy,
|
||||||
widget::{label::WidgetLabel, rectangle::WidgetRectangle, sprite::WidgetSprite},
|
widget::{
|
||||||
|
image::WidgetImage, label::WidgetLabel, rectangle::WidgetRectangle, sprite::WidgetSprite,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use wlx_common::windowing::OverlayWindowState;
|
use wlx_common::windowing::OverlayWindowState;
|
||||||
|
|
||||||
@@ -111,23 +113,26 @@ fn apply_custom_command(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ModifyPanelCommand::SetSprite(path) => {
|
ModifyPanelCommand::SetSprite(path) => {
|
||||||
let mut widget = panel
|
if let Ok(pair) = panel
|
||||||
.parser_state
|
.parser_state
|
||||||
.fetch_widget_as::<WidgetSprite>(&panel.layout.state, element)
|
.fetch_widget(&panel.layout.state, element)
|
||||||
.context("No <sprite> with such id.")?;
|
{
|
||||||
|
|
||||||
if path == "none" {
|
|
||||||
widget.set_content(&mut com, None);
|
|
||||||
} else {
|
|
||||||
let content = CustomGlyphContent::from_assets(
|
let content = CustomGlyphContent::from_assets(
|
||||||
&mut app.wgui_globals,
|
&mut app.wgui_globals,
|
||||||
wgui::assets::AssetPath::File(&path),
|
wgui::assets::AssetPath::File(&path),
|
||||||
)
|
)
|
||||||
.context("Could not load content from supplied path.")?;
|
.context("Could not load content from supplied path.")?;
|
||||||
|
|
||||||
let data = CustomGlyphData::new(content);
|
let data = CustomGlyphData::new(content);
|
||||||
|
|
||||||
widget.set_content(&mut com, Some(data));
|
if let Some(mut sprite) = pair.widget.get_as_mut::<WidgetSprite>() {
|
||||||
|
sprite.set_content(&mut com, Some(data));
|
||||||
|
} else if let Some(mut image) = pair.widget.get_as_mut::<WidgetImage>() {
|
||||||
|
image.set_content(&mut com, Some(data));
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("No <sprite> or <image> with such id.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("No <sprite> or <image> with such id.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ModifyPanelCommand::SetColor(color) => {
|
ModifyPanelCommand::SetColor(color) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user