mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'imbris/tooltip-enhancements' into 'master'
Add ImageFrame widget, make tooltips use this, align ui to the pixels properly See merge request veloren/veloren!474
This commit is contained in:
BIN
assets/voxygen/element/frames/tt_test_corner_tr.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/tt_test_corner_tr.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/tt_test_edge.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/tt_test_edge.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -56,7 +56,7 @@ pub fn rgb_to_hsv(rgb: Rgb<f32>) -> Vec3<f32> {
|
|||||||
let h = if max == min {
|
let h = if max == min {
|
||||||
0.0
|
0.0
|
||||||
} else {
|
} else {
|
||||||
let mut h = (60.0 * (add + diff / (max - min)));
|
let mut h = 60.0 * (add + diff / (max - min));
|
||||||
if h < 0.0 {
|
if h < 0.0 {
|
||||||
h += 360.0;
|
h += 360.0;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use crate::{
|
|||||||
ui::{
|
ui::{
|
||||||
self,
|
self,
|
||||||
img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic},
|
img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic},
|
||||||
Tooltip, Tooltipable, Ui,
|
ImageFrame, Tooltip, Tooltipable, Ui,
|
||||||
},
|
},
|
||||||
GlobalState,
|
GlobalState,
|
||||||
};
|
};
|
||||||
@ -78,7 +78,16 @@ image_ids! {
|
|||||||
<BlankGraphic>
|
<BlankGraphic>
|
||||||
nothing: (),
|
nothing: (),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rotation_image_ids! {
|
||||||
|
pub struct ImgsRot {
|
||||||
|
<VoxelGraphic>
|
||||||
|
|
||||||
|
// Tooltip Test
|
||||||
|
tt_side: "voxygen/element/frames/tt_test_edge",
|
||||||
|
tt_corner: "voxygen/element/frames/tt_test_corner_tr",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
font_ids! {
|
font_ids! {
|
||||||
@ -104,6 +113,7 @@ pub struct MainMenuUi {
|
|||||||
ui: Ui,
|
ui: Ui,
|
||||||
ids: Ids,
|
ids: Ids,
|
||||||
imgs: Imgs,
|
imgs: Imgs,
|
||||||
|
rot_imgs: ImgsRot,
|
||||||
fonts: Fonts,
|
fonts: Fonts,
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
@ -126,6 +136,7 @@ impl MainMenuUi {
|
|||||||
let ids = Ids::new(ui.id_generator());
|
let ids = Ids::new(ui.id_generator());
|
||||||
// Load images
|
// Load images
|
||||||
let imgs = Imgs::load(&mut ui).expect("Failed to load images");
|
let imgs = Imgs::load(&mut ui).expect("Failed to load images");
|
||||||
|
let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load images!");
|
||||||
// Load fonts
|
// Load fonts
|
||||||
let fonts = Fonts::load(&mut ui).expect("Failed to load fonts");
|
let fonts = Fonts::load(&mut ui).expect("Failed to load fonts");
|
||||||
|
|
||||||
@ -133,6 +144,7 @@ impl MainMenuUi {
|
|||||||
ui,
|
ui,
|
||||||
ids,
|
ids,
|
||||||
imgs,
|
imgs,
|
||||||
|
rot_imgs,
|
||||||
fonts,
|
fonts,
|
||||||
username: networking.username.clone(),
|
username: networking.username.clone(),
|
||||||
password: "".to_owned(),
|
password: "".to_owned(),
|
||||||
@ -150,6 +162,25 @@ impl MainMenuUi {
|
|||||||
let version = env!("CARGO_PKG_VERSION");
|
let version = env!("CARGO_PKG_VERSION");
|
||||||
const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0);
|
const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0);
|
||||||
const TEXT_COLOR_2: Color = Color::Rgba(1.0, 1.0, 1.0, 0.2);
|
const TEXT_COLOR_2: Color = Color::Rgba(1.0, 1.0, 1.0, 0.2);
|
||||||
|
|
||||||
|
// Tooltip
|
||||||
|
let tooltip = Tooltip::new({
|
||||||
|
// Edge images [t, b, r, l]
|
||||||
|
// Corner images [tr, tl, br, bl]
|
||||||
|
let edge = &self.rot_imgs.tt_side;
|
||||||
|
let corner = &self.rot_imgs.tt_corner;
|
||||||
|
ImageFrame::new(
|
||||||
|
[edge.cw180, edge.none, edge.cw270, edge.cw90],
|
||||||
|
[corner.none, corner.cw270, corner.cw90, corner.cw180],
|
||||||
|
Color::Rgba(0.08, 0.07, 0.04, 1.0),
|
||||||
|
5.0,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.title_font_size(15)
|
||||||
|
.desc_font_size(10)
|
||||||
|
.title_text_color(TEXT_COLOR)
|
||||||
|
.desc_text_color(TEXT_COLOR_2);
|
||||||
|
|
||||||
// Background image, Veloren logo, Alpha-Version Label
|
// Background image, Veloren logo, Alpha-Version Label
|
||||||
Image::new(self.imgs.bg)
|
Image::new(self.imgs.bg)
|
||||||
.middle_of(ui_widgets.window)
|
.middle_of(ui_widgets.window)
|
||||||
@ -158,6 +189,7 @@ impl MainMenuUi {
|
|||||||
.w_h(123.0 * 3.0, 35.0 * 3.0)
|
.w_h(123.0 * 3.0, 35.0 * 3.0)
|
||||||
.top_right_with_margins(30.0, 30.0)
|
.top_right_with_margins(30.0, 30.0)
|
||||||
.set(self.ids.v_logo, ui_widgets);
|
.set(self.ids.v_logo, ui_widgets);
|
||||||
|
|
||||||
Text::new(version)
|
Text::new(version)
|
||||||
.top_right_with_margins_on(ui_widgets.window, 5.0, 5.0)
|
.top_right_with_margins_on(ui_widgets.window, 5.0, 5.0)
|
||||||
.font_size(14)
|
.font_size(14)
|
||||||
@ -467,12 +499,11 @@ impl MainMenuUi {
|
|||||||
.label_y(Relative::Scalar(5.0))
|
.label_y(Relative::Scalar(5.0))
|
||||||
.with_tooltip(
|
.with_tooltip(
|
||||||
tooltip_manager,
|
tooltip_manager,
|
||||||
Tooltip::new("Login", "Click to login with the entered details")
|
"Login",
|
||||||
.title_font_size(15)
|
"Click to login with the entered details",
|
||||||
.desc_font_size(10)
|
&tooltip,
|
||||||
.title_text_color(TEXT_COLOR)
|
|
||||||
.desc_text_color(TEXT_COLOR_2),
|
|
||||||
)
|
)
|
||||||
|
.tooltip_image(self.imgs.v_logo)
|
||||||
.set(self.ids.login_button, ui_widgets)
|
.set(self.ids.login_button, ui_widgets)
|
||||||
.was_clicked()
|
.was_clicked()
|
||||||
{
|
{
|
||||||
|
@ -97,12 +97,27 @@ pub fn create_quad(
|
|||||||
|
|
||||||
let (l, b, r, t) = aabr_to_lbrt(rect);
|
let (l, b, r, t) = aabr_to_lbrt(rect);
|
||||||
let (uv_l, uv_b, uv_r, uv_t) = aabr_to_lbrt(uv_rect);
|
let (uv_l, uv_b, uv_r, uv_t) = aabr_to_lbrt(uv_rect);
|
||||||
Quad::new(
|
|
||||||
v([r, t], [uv_r, uv_t]),
|
match (uv_b > uv_t, uv_l > uv_r) {
|
||||||
v([l, t], [uv_l, uv_t]),
|
(true, true) => Quad::new(
|
||||||
v([l, b], [uv_l, uv_b]),
|
v([r, t], [uv_l, uv_b]),
|
||||||
v([r, b], [uv_r, uv_b]),
|
v([l, t], [uv_l, uv_t]),
|
||||||
)
|
v([l, b], [uv_r, uv_t]),
|
||||||
|
v([r, b], [uv_r, uv_b]),
|
||||||
|
),
|
||||||
|
(false, false) => Quad::new(
|
||||||
|
v([r, t], [uv_l, uv_b]),
|
||||||
|
v([l, t], [uv_l, uv_t]),
|
||||||
|
v([l, b], [uv_r, uv_t]),
|
||||||
|
v([r, b], [uv_r, uv_b]),
|
||||||
|
),
|
||||||
|
_ => Quad::new(
|
||||||
|
v([r, t], [uv_r, uv_t]),
|
||||||
|
v([l, t], [uv_l, uv_t]),
|
||||||
|
v([l, b], [uv_l, uv_b]),
|
||||||
|
v([r, b], [uv_r, uv_b]),
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_tri(
|
pub fn create_tri(
|
||||||
|
@ -186,7 +186,7 @@ impl Renderer {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queue the clearing of the color and depth targets ready for a new frame to be rendered.
|
/// Queue the clearing of the depth target ready for a new frame to be rendered.
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.encoder.clear_depth(&self.tgt_depth_view, 1.0);
|
self.encoder.clear_depth(&self.tgt_depth_view, 1.0);
|
||||||
self.encoder.clear_depth(&self.win_depth_view, 1.0);
|
self.encoder.clear_depth(&self.win_depth_view, 1.0);
|
||||||
|
@ -1,246 +0,0 @@
|
|||||||
use dot_vox::DotVoxData;
|
|
||||||
use guillotiere::{size2, AllocId, Allocation, AtlasAllocator};
|
|
||||||
use hashbrown::HashMap;
|
|
||||||
use image::{DynamicImage, RgbaImage};
|
|
||||||
use log::{error, warn};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use vek::*;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum Graphic {
|
|
||||||
Image(Arc<DynamicImage>),
|
|
||||||
Voxel(Arc<DotVoxData>, Option<u8>),
|
|
||||||
Blank,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
|
|
||||||
pub struct Id(u32);
|
|
||||||
|
|
||||||
type Parameters = (Id, Vec2<u16>, Aabr<u64>);
|
|
||||||
|
|
||||||
pub struct CachedDetails {
|
|
||||||
// Id used by AtlasAllocator
|
|
||||||
alloc_id: AllocId,
|
|
||||||
// Last frame this was used on
|
|
||||||
frame: u32,
|
|
||||||
// Where in the cache texture this is
|
|
||||||
aabr: Aabr<u16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GraphicCache {
|
|
||||||
graphic_map: HashMap<Id, Graphic>,
|
|
||||||
next_id: u32,
|
|
||||||
|
|
||||||
atlas: AtlasAllocator,
|
|
||||||
cache_map: HashMap<Parameters, CachedDetails>,
|
|
||||||
// The current frame
|
|
||||||
current_frame: u32,
|
|
||||||
unused_entries_this_frame: Option<Vec<Option<(u32, Parameters)>>>,
|
|
||||||
|
|
||||||
soft_cache: HashMap<Parameters, RgbaImage>,
|
|
||||||
transfer_ready: Vec<(Parameters, Aabr<u16>)>,
|
|
||||||
}
|
|
||||||
impl GraphicCache {
|
|
||||||
pub fn new(size: Vec2<u16>) -> Self {
|
|
||||||
Self {
|
|
||||||
graphic_map: HashMap::default(),
|
|
||||||
next_id: 0,
|
|
||||||
atlas: AtlasAllocator::new(size2(i32::from(size.x), i32::from(size.y))),
|
|
||||||
cache_map: HashMap::default(),
|
|
||||||
current_frame: 0,
|
|
||||||
unused_entries_this_frame: None,
|
|
||||||
soft_cache: HashMap::default(),
|
|
||||||
transfer_ready: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn add_graphic(&mut self, graphic: Graphic) -> Id {
|
|
||||||
let id = self.next_id;
|
|
||||||
self.next_id = id.wrapping_add(1);
|
|
||||||
|
|
||||||
let id = Id(id);
|
|
||||||
self.graphic_map.insert(id, graphic);
|
|
||||||
|
|
||||||
id
|
|
||||||
}
|
|
||||||
pub fn get_graphic(&self, id: Id) -> Option<&Graphic> {
|
|
||||||
self.graphic_map.get(&id)
|
|
||||||
}
|
|
||||||
pub fn clear_cache(&mut self, new_size: Vec2<u16>) {
|
|
||||||
self.soft_cache.clear();
|
|
||||||
self.transfer_ready.clear();
|
|
||||||
self.cache_map.clear();
|
|
||||||
self.atlas = AtlasAllocator::new(size2(i32::from(new_size.x), i32::from(new_size.y)));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn queue_res(
|
|
||||||
&mut self,
|
|
||||||
graphic_id: Id,
|
|
||||||
dims: Vec2<u16>,
|
|
||||||
source: Aabr<f64>,
|
|
||||||
) -> Option<Aabr<u16>> {
|
|
||||||
let key = (graphic_id, dims, source.map(|e| e.to_bits())); // TODO: Replace this with rounded representation of source
|
|
||||||
|
|
||||||
if let Some(details) = self.cache_map.get_mut(&key) {
|
|
||||||
// Update frame
|
|
||||||
details.frame = self.current_frame;
|
|
||||||
|
|
||||||
Some(details.aabr)
|
|
||||||
} else {
|
|
||||||
// Create image if it doesn't already exist
|
|
||||||
if !self.soft_cache.contains_key(&key) {
|
|
||||||
self.soft_cache.insert(
|
|
||||||
key,
|
|
||||||
match self.graphic_map.get(&graphic_id) {
|
|
||||||
Some(Graphic::Blank) => return None,
|
|
||||||
// Render image at requested resolution
|
|
||||||
// TODO: Use source aabr.
|
|
||||||
Some(Graphic::Image(ref image)) => image
|
|
||||||
.resize_exact(
|
|
||||||
u32::from(dims.x),
|
|
||||||
u32::from(dims.y),
|
|
||||||
image::FilterType::Nearest,
|
|
||||||
)
|
|
||||||
.to_rgba(),
|
|
||||||
Some(Graphic::Voxel(ref vox, min_samples)) => {
|
|
||||||
super::renderer::draw_vox(&vox.as_ref().into(), dims, *min_samples)
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
warn!("A graphic was requested via an id which is not in use");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let aabr_from_alloc_rect = |rect: guillotiere::Rectangle| {
|
|
||||||
let (min, max) = (rect.min, rect.max);
|
|
||||||
Aabr {
|
|
||||||
min: Vec2::new(min.x as u16, min.y as u16),
|
|
||||||
max: Vec2::new(max.x as u16, max.y as u16),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Allocate rectangle.
|
|
||||||
let (alloc_id, aabr) = match self
|
|
||||||
.atlas
|
|
||||||
.allocate(size2(i32::from(dims.x), i32::from(dims.y)))
|
|
||||||
{
|
|
||||||
Some(Allocation { id, rectangle }) => (id, aabr_from_alloc_rect(rectangle)),
|
|
||||||
// Out of room.
|
|
||||||
// 1) Remove unused allocations
|
|
||||||
// TODO: Make more room.
|
|
||||||
// 2) Rearrange rectangles (see comments below)
|
|
||||||
// 3) Expand cache size
|
|
||||||
None => {
|
|
||||||
// 1) Remove unused allocations
|
|
||||||
if self.unused_entries_this_frame.is_none() {
|
|
||||||
self.unused_entries_this_frame = {
|
|
||||||
let mut unused = self
|
|
||||||
.cache_map
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(key, details)| {
|
|
||||||
if details.frame < self.current_frame - 1 {
|
|
||||||
Some(Some((details.frame, *key)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
unused
|
|
||||||
.sort_unstable_by(|a, b| a.map(|(f, _)| f).cmp(&b.map(|(f, _)| f)));
|
|
||||||
Some(unused)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut allocation = None;
|
|
||||||
// Fight the checker!
|
|
||||||
let current_frame = self.current_frame;
|
|
||||||
// Will always be Some
|
|
||||||
if let Some(ref mut unused_entries) = self.unused_entries_this_frame {
|
|
||||||
// Deallocate from oldest to newest
|
|
||||||
for key in unused_entries
|
|
||||||
.iter_mut()
|
|
||||||
.filter_map(|e| e.take().map(|(_, key)| key))
|
|
||||||
{
|
|
||||||
// Check if still in cache map and it has not been used since the vec was built
|
|
||||||
if self
|
|
||||||
.cache_map
|
|
||||||
.get(&key)
|
|
||||||
.filter(|d| d.frame != current_frame)
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
if let Some(alloc_id) =
|
|
||||||
self.cache_map.remove(&key).map(|d| d.alloc_id)
|
|
||||||
{
|
|
||||||
// Deallocate
|
|
||||||
self.atlas.deallocate(alloc_id);
|
|
||||||
// Try to allocate
|
|
||||||
if let Some(alloc) = self
|
|
||||||
.atlas
|
|
||||||
.allocate(size2(i32::from(dims.x), i32::from(dims.y)))
|
|
||||||
{
|
|
||||||
allocation = Some(alloc);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 2) Rearrange rectangles
|
|
||||||
// This needs to be done infrequently and be based on whether rectangles have been removed
|
|
||||||
// Maybe find a way to calculate whether there is a significant amount of fragmentation
|
|
||||||
// Or consider dropping the use of an atlas and moving to a hashmap of individual textures :/
|
|
||||||
// if allocation.is_none() {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
match allocation {
|
|
||||||
Some(Allocation { id, rectangle }) => (id, aabr_from_alloc_rect(rectangle)),
|
|
||||||
None => {
|
|
||||||
warn!("Can't find space for an image in the graphic cache");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.transfer_ready.push((key, aabr));
|
|
||||||
|
|
||||||
// Insert area into map for retrieval.
|
|
||||||
self.cache_map.insert(
|
|
||||||
key,
|
|
||||||
CachedDetails {
|
|
||||||
alloc_id,
|
|
||||||
frame: self.current_frame,
|
|
||||||
aabr,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Some(aabr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Anything not queued since the last call to this will be removed if there is not enough space in the cache
|
|
||||||
pub fn cache_queued<F>(&mut self, mut cacher: F)
|
|
||||||
where
|
|
||||||
F: FnMut(Aabr<u16>, &[[u8; 4]]),
|
|
||||||
{
|
|
||||||
// Cached queued
|
|
||||||
// TODO: combine nearby transfers
|
|
||||||
for (key, target_aarb) in self.transfer_ready.drain(..) {
|
|
||||||
if let Some(image) = self.soft_cache.get(&key) {
|
|
||||||
cacher(
|
|
||||||
target_aarb,
|
|
||||||
&image.pixels().map(|p| p.0).collect::<Vec<[u8; 4]>>(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
error!("Image queued for transfer to gpu cache but it doesn't exist (this should never occur)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment frame
|
|
||||||
self.current_frame += 1;
|
|
||||||
|
|
||||||
// Reset unused entries
|
|
||||||
self.unused_entries_this_frame = None;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,274 @@
|
|||||||
mod graphic;
|
|
||||||
mod renderer;
|
mod renderer;
|
||||||
|
|
||||||
pub use graphic::{Graphic, GraphicCache, Id};
|
use dot_vox::DotVoxData;
|
||||||
|
use guillotiere::{size2, AllocId, Allocation, AtlasAllocator};
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use image::{DynamicImage, RgbaImage};
|
||||||
|
use log::{error, warn};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Graphic {
|
||||||
|
Image(Arc<DynamicImage>),
|
||||||
|
Voxel(Arc<DotVoxData>, Option<Quaternion<f32>>, Option<u8>),
|
||||||
|
Blank,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Rotation {
|
||||||
|
None,
|
||||||
|
Cw90,
|
||||||
|
Cw180,
|
||||||
|
Cw270,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
|
||||||
|
pub struct Id(u32);
|
||||||
|
|
||||||
|
type Parameters = (Id, Vec2<u16>, Aabr<u64>);
|
||||||
|
|
||||||
|
struct CachedDetails {
|
||||||
|
// Id used by AtlasAllocator
|
||||||
|
alloc_id: AllocId,
|
||||||
|
// Last frame this was used on
|
||||||
|
frame: u32,
|
||||||
|
// Where in the cache texture this is
|
||||||
|
aabr: Aabr<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GraphicCache {
|
||||||
|
graphic_map: HashMap<Id, Graphic>,
|
||||||
|
next_id: u32,
|
||||||
|
|
||||||
|
atlas: AtlasAllocator,
|
||||||
|
cache_map: HashMap<Parameters, CachedDetails>,
|
||||||
|
// The current frame
|
||||||
|
current_frame: u32,
|
||||||
|
unused_entries_this_frame: Option<Vec<Option<(u32, Parameters)>>>,
|
||||||
|
|
||||||
|
soft_cache: HashMap<Parameters, RgbaImage>,
|
||||||
|
transfer_ready: Vec<(Parameters, Aabr<u16>)>,
|
||||||
|
}
|
||||||
|
impl GraphicCache {
|
||||||
|
pub fn new(size: Vec2<u16>) -> Self {
|
||||||
|
Self {
|
||||||
|
graphic_map: HashMap::default(),
|
||||||
|
next_id: 0,
|
||||||
|
atlas: AtlasAllocator::new(size2(i32::from(size.x), i32::from(size.y))),
|
||||||
|
cache_map: HashMap::default(),
|
||||||
|
current_frame: 0,
|
||||||
|
unused_entries_this_frame: None,
|
||||||
|
soft_cache: HashMap::default(),
|
||||||
|
transfer_ready: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn add_graphic(&mut self, graphic: Graphic) -> Id {
|
||||||
|
let id = self.next_id;
|
||||||
|
self.next_id = id.wrapping_add(1);
|
||||||
|
|
||||||
|
let id = Id(id);
|
||||||
|
self.graphic_map.insert(id, graphic);
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
pub fn get_graphic(&self, id: Id) -> Option<&Graphic> {
|
||||||
|
self.graphic_map.get(&id)
|
||||||
|
}
|
||||||
|
pub fn clear_cache(&mut self, new_size: Vec2<u16>) {
|
||||||
|
self.soft_cache.clear();
|
||||||
|
self.transfer_ready.clear();
|
||||||
|
self.cache_map.clear();
|
||||||
|
self.atlas = AtlasAllocator::new(size2(i32::from(new_size.x), i32::from(new_size.y)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queue_res(
|
||||||
|
&mut self,
|
||||||
|
graphic_id: Id,
|
||||||
|
dims: Vec2<u16>,
|
||||||
|
source: Aabr<f64>,
|
||||||
|
rotation: Rotation,
|
||||||
|
) -> Option<Aabr<u16>> {
|
||||||
|
let dims = match rotation {
|
||||||
|
Rotation::Cw90 | Rotation::Cw270 => Vec2::new(dims.y, dims.x),
|
||||||
|
Rotation::None | Rotation::Cw180 => dims,
|
||||||
|
};
|
||||||
|
let key = (graphic_id, dims, source.map(|e| e.to_bits())); // TODO: Replace this with rounded representation of source
|
||||||
|
|
||||||
|
let rotated_aabr = |Aabr { min, max }| match rotation {
|
||||||
|
Rotation::None => Aabr { min, max },
|
||||||
|
Rotation::Cw90 => Aabr {
|
||||||
|
min: Vec2::new(min.x, max.y),
|
||||||
|
max: Vec2::new(max.x, min.y),
|
||||||
|
},
|
||||||
|
Rotation::Cw180 => Aabr { min: max, max: min },
|
||||||
|
Rotation::Cw270 => Aabr {
|
||||||
|
min: Vec2::new(max.x, min.y),
|
||||||
|
max: Vec2::new(min.x, max.y),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(details) = self.cache_map.get_mut(&key) {
|
||||||
|
// Update frame
|
||||||
|
details.frame = self.current_frame;
|
||||||
|
|
||||||
|
Some(rotated_aabr(details.aabr))
|
||||||
|
} else {
|
||||||
|
// Create image if it doesn't already exist
|
||||||
|
if !self.soft_cache.contains_key(&key) {
|
||||||
|
self.soft_cache.insert(
|
||||||
|
key,
|
||||||
|
match self.graphic_map.get(&graphic_id) {
|
||||||
|
Some(Graphic::Blank) => return None,
|
||||||
|
// Render image at requested resolution
|
||||||
|
// TODO: Use source aabr.
|
||||||
|
Some(Graphic::Image(ref image)) => image
|
||||||
|
.resize_exact(
|
||||||
|
u32::from(dims.x),
|
||||||
|
u32::from(dims.y),
|
||||||
|
image::FilterType::Nearest,
|
||||||
|
)
|
||||||
|
.to_rgba(),
|
||||||
|
Some(Graphic::Voxel(ref vox, ori, min_samples)) => {
|
||||||
|
renderer::draw_vox(&vox.as_ref().into(), dims, *ori, *min_samples)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
warn!("A graphic was requested via an id which is not in use");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let aabr_from_alloc_rect = |rect: guillotiere::Rectangle| {
|
||||||
|
let (min, max) = (rect.min, rect.max);
|
||||||
|
Aabr {
|
||||||
|
min: Vec2::new(min.x as u16, min.y as u16),
|
||||||
|
max: Vec2::new(max.x as u16, max.y as u16),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allocate rectangle.
|
||||||
|
let (alloc_id, aabr) = match self
|
||||||
|
.atlas
|
||||||
|
.allocate(size2(i32::from(dims.x), i32::from(dims.y)))
|
||||||
|
{
|
||||||
|
Some(Allocation { id, rectangle }) => (id, aabr_from_alloc_rect(rectangle)),
|
||||||
|
// Out of room.
|
||||||
|
// 1) Remove unused allocations
|
||||||
|
// TODO: Make more room.
|
||||||
|
// 2) Rearrange rectangles (see comments below)
|
||||||
|
// 3) Expand cache size
|
||||||
|
None => {
|
||||||
|
// 1) Remove unused allocations
|
||||||
|
if self.unused_entries_this_frame.is_none() {
|
||||||
|
self.unused_entries_this_frame = {
|
||||||
|
let mut unused = self
|
||||||
|
.cache_map
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(key, details)| {
|
||||||
|
if details.frame < self.current_frame - 1 {
|
||||||
|
Some(Some((details.frame, *key)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
unused
|
||||||
|
.sort_unstable_by(|a, b| a.map(|(f, _)| f).cmp(&b.map(|(f, _)| f)));
|
||||||
|
Some(unused)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut allocation = None;
|
||||||
|
// Fight the checker!
|
||||||
|
let current_frame = self.current_frame;
|
||||||
|
// Will always be Some
|
||||||
|
if let Some(ref mut unused_entries) = self.unused_entries_this_frame {
|
||||||
|
// Deallocate from oldest to newest
|
||||||
|
for key in unused_entries
|
||||||
|
.iter_mut()
|
||||||
|
.filter_map(|e| e.take().map(|(_, key)| key))
|
||||||
|
{
|
||||||
|
// Check if still in cache map and it has not been used since the vec was built
|
||||||
|
if self
|
||||||
|
.cache_map
|
||||||
|
.get(&key)
|
||||||
|
.filter(|d| d.frame != current_frame)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
if let Some(alloc_id) =
|
||||||
|
self.cache_map.remove(&key).map(|d| d.alloc_id)
|
||||||
|
{
|
||||||
|
// Deallocate
|
||||||
|
self.atlas.deallocate(alloc_id);
|
||||||
|
// Try to allocate
|
||||||
|
if let Some(alloc) = self
|
||||||
|
.atlas
|
||||||
|
.allocate(size2(i32::from(dims.x), i32::from(dims.y)))
|
||||||
|
{
|
||||||
|
allocation = Some(alloc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2) Rearrange rectangles
|
||||||
|
// This needs to be done infrequently and be based on whether rectangles have been removed
|
||||||
|
// Maybe find a way to calculate whether there is a significant amount of fragmentation
|
||||||
|
// Or consider dropping the use of an atlas and moving to a hashmap of individual textures :/
|
||||||
|
// if allocation.is_none() {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
match allocation {
|
||||||
|
Some(Allocation { id, rectangle }) => (id, aabr_from_alloc_rect(rectangle)),
|
||||||
|
None => {
|
||||||
|
warn!("Can't find space for an image in the graphic cache");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.transfer_ready.push((key, aabr));
|
||||||
|
|
||||||
|
// Insert area into map for retrieval.
|
||||||
|
self.cache_map.insert(
|
||||||
|
key,
|
||||||
|
CachedDetails {
|
||||||
|
alloc_id,
|
||||||
|
frame: self.current_frame,
|
||||||
|
aabr,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(rotated_aabr(aabr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anything not queued since the last call to this will be removed if there is not enough space in the cache
|
||||||
|
pub fn cache_queued<F>(&mut self, mut cacher: F)
|
||||||
|
where
|
||||||
|
F: FnMut(Aabr<u16>, &[[u8; 4]]),
|
||||||
|
{
|
||||||
|
// Cached queued
|
||||||
|
// TODO: combine nearby transfers
|
||||||
|
for (key, target_aarb) in self.transfer_ready.drain(..) {
|
||||||
|
if let Some(image) = self.soft_cache.get(&key) {
|
||||||
|
cacher(
|
||||||
|
target_aarb,
|
||||||
|
&image.pixels().map(|p| p.0).collect::<Vec<[u8; 4]>>(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
error!("Image queued for transfer to gpu cache but it doesn't exist (this should never occur)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment frame
|
||||||
|
self.current_frame += 1;
|
||||||
|
|
||||||
|
// Reset unused entries
|
||||||
|
self.unused_entries_this_frame = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -58,7 +58,12 @@ impl<'a> Pipeline for Voxel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_vox(segment: &Segment, output_size: Vec2<u16>, min_samples: Option<u8>) -> RgbaImage {
|
pub fn draw_vox(
|
||||||
|
segment: &Segment,
|
||||||
|
output_size: Vec2<u16>,
|
||||||
|
ori: Option<Quaternion<f32>>,
|
||||||
|
min_samples: Option<u8>,
|
||||||
|
) -> RgbaImage {
|
||||||
let scale = min_samples.map_or(1.0, |s| s as f32).sqrt().ceil() as usize;
|
let scale = min_samples.map_or(1.0, |s| s as f32).sqrt().ceil() as usize;
|
||||||
let dims = output_size.map(|e| e as usize * scale).into_array();
|
let dims = output_size.map(|e| e as usize * scale).into_array();
|
||||||
let mut color = Buffer2d::new(dims, [0; 4]);
|
let mut color = Buffer2d::new(dims, [0; 4]);
|
||||||
@ -73,7 +78,8 @@ pub fn draw_vox(segment: &Segment, output_size: Vec2<u16>, min_samples: Option<u
|
|||||||
top: 1.0,
|
top: 1.0,
|
||||||
near: 0.0,
|
near: 0.0,
|
||||||
far: 1.0,
|
far: 1.0,
|
||||||
}) * Mat4::rotation_x(-std::f32::consts::PI / 2.0)
|
}) * Mat4::from(ori.unwrap_or(Quaternion::identity()))
|
||||||
|
* Mat4::rotation_x(-std::f32::consts::PI / 2.0) // TODO: remove
|
||||||
* Mat4::scaling_3d([2.0 / w, 2.0 / h, 2.0 / d])
|
* Mat4::scaling_3d([2.0 / w, 2.0 / h, 2.0 / d])
|
||||||
* Mat4::translation_3d([-w / 2.0, -h / 2.0, -d / 2.0]);
|
* Mat4::translation_3d([-w / 2.0, -h / 2.0, -d / 2.0]);
|
||||||
Voxel { mvp }.draw::<rasterizer::Triangles<_>, _>(
|
Voxel { mvp }.draw::<rasterizer::Triangles<_>, _>(
|
||||||
|
@ -31,7 +31,7 @@ pub enum VoxelMs9Graphic {}
|
|||||||
impl<'a> GraphicCreator<'a> for VoxelGraphic {
|
impl<'a> GraphicCreator<'a> for VoxelGraphic {
|
||||||
type Specifier = &'a str;
|
type Specifier = &'a str;
|
||||||
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
|
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
|
||||||
Ok(Graphic::Voxel(load::<DotVoxData>(specifier)?, None))
|
Ok(Graphic::Voxel(load::<DotVoxData>(specifier)?, None, None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> GraphicCreator<'a> for VoxelMsGraphic {
|
impl<'a> GraphicCreator<'a> for VoxelMsGraphic {
|
||||||
@ -39,6 +39,7 @@ impl<'a> GraphicCreator<'a> for VoxelMsGraphic {
|
|||||||
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
|
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
|
||||||
Ok(Graphic::Voxel(
|
Ok(Graphic::Voxel(
|
||||||
load::<DotVoxData>(specifier.0)?,
|
load::<DotVoxData>(specifier.0)?,
|
||||||
|
None,
|
||||||
Some(specifier.1),
|
Some(specifier.1),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -46,16 +47,31 @@ impl<'a> GraphicCreator<'a> for VoxelMsGraphic {
|
|||||||
impl<'a> GraphicCreator<'a> for VoxelMs4Graphic {
|
impl<'a> GraphicCreator<'a> for VoxelMs4Graphic {
|
||||||
type Specifier = &'a str;
|
type Specifier = &'a str;
|
||||||
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
|
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
|
||||||
Ok(Graphic::Voxel(load::<DotVoxData>(specifier)?, Some(4)))
|
Ok(Graphic::Voxel(
|
||||||
|
load::<DotVoxData>(specifier)?,
|
||||||
|
None,
|
||||||
|
Some(4),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> GraphicCreator<'a> for VoxelMs9Graphic {
|
impl<'a> GraphicCreator<'a> for VoxelMs9Graphic {
|
||||||
type Specifier = &'a str;
|
type Specifier = &'a str;
|
||||||
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
|
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
|
||||||
Ok(Graphic::Voxel(load::<DotVoxData>(specifier)?, Some(9)))
|
Ok(Graphic::Voxel(
|
||||||
|
load::<DotVoxData>(specifier)?,
|
||||||
|
None,
|
||||||
|
Some(9),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Rotations {
|
||||||
|
pub none: conrod_core::image::Id,
|
||||||
|
pub cw90: conrod_core::image::Id,
|
||||||
|
pub cw180: conrod_core::image::Id,
|
||||||
|
pub cw270: conrod_core::image::Id,
|
||||||
|
}
|
||||||
|
|
||||||
/// This macro will automatically load all specified assets, get the corresponding ImgIds and
|
/// This macro will automatically load all specified assets, get the corresponding ImgIds and
|
||||||
/// create a struct with all of them.
|
/// create a struct with all of them.
|
||||||
///
|
///
|
||||||
@ -80,7 +96,7 @@ macro_rules! image_ids {
|
|||||||
($($v:vis struct $Ids:ident { $( <$T:ty> $( $name:ident: $specifier:expr ),* $(,)? )* })*) => {
|
($($v:vis struct $Ids:ident { $( <$T:ty> $( $name:ident: $specifier:expr ),* $(,)? )* })*) => {
|
||||||
$(
|
$(
|
||||||
$v struct $Ids {
|
$v struct $Ids {
|
||||||
$($( $v $name: conrod_core::image::Id, )*)*
|
$($( $v $name: conrod_core::image::Id, )*)*
|
||||||
}
|
}
|
||||||
|
|
||||||
impl $Ids {
|
impl $Ids {
|
||||||
@ -94,3 +110,24 @@ macro_rules! image_ids {
|
|||||||
)*
|
)*
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: combine with the img_ids macro above using a marker for specific fields that should be `Rotations` instead of `widget::Id`
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! rotation_image_ids {
|
||||||
|
($($v:vis struct $Ids:ident { $( <$T:ty> $( $name:ident: $specifier:expr ),* $(,)? )* })*) => {
|
||||||
|
$(
|
||||||
|
$v struct $Ids {
|
||||||
|
$($( $v $name: crate::ui::img_ids::Rotations, )*)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $Ids {
|
||||||
|
pub fn load(ui: &mut crate::ui::Ui) -> Result<Self, common::assets::Error> {
|
||||||
|
use crate::ui::img_ids::GraphicCreator;
|
||||||
|
Ok(Self {
|
||||||
|
$($( $name: ui.add_graphic_with_rotations(<$T as GraphicCreator>::new_graphic($specifier)?), )*)*
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -12,6 +12,7 @@ pub use event::Event;
|
|||||||
pub use graphic::Graphic;
|
pub use graphic::Graphic;
|
||||||
pub use scale::{Scale, ScaleMode};
|
pub use scale::{Scale, ScaleMode};
|
||||||
pub use widgets::{
|
pub use widgets::{
|
||||||
|
image_frame::ImageFrame,
|
||||||
image_slider::ImageSlider,
|
image_slider::ImageSlider,
|
||||||
ingame::{Ingame, IngameAnchor, Ingameable},
|
ingame::{Ingame, IngameAnchor, Ingameable},
|
||||||
toggle_button::ToggleButton,
|
toggle_button::ToggleButton,
|
||||||
@ -38,7 +39,7 @@ use conrod_core::{
|
|||||||
widget::{self, id::Generator},
|
widget::{self, id::Generator},
|
||||||
Rect, UiBuilder, UiCell,
|
Rect, UiBuilder, UiCell,
|
||||||
};
|
};
|
||||||
use graphic::Id as GraphicId;
|
use graphic::Rotation;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
@ -92,7 +93,7 @@ impl assets::Asset for Font {
|
|||||||
|
|
||||||
pub struct Ui {
|
pub struct Ui {
|
||||||
ui: conrod_core::Ui,
|
ui: conrod_core::Ui,
|
||||||
image_map: Map<GraphicId>,
|
image_map: Map<(graphic::Id, Rotation)>,
|
||||||
cache: Cache,
|
cache: Cache,
|
||||||
// Draw commands for the next render
|
// Draw commands for the next render
|
||||||
draw_commands: Vec<DrawCommand>,
|
draw_commands: Vec<DrawCommand>,
|
||||||
@ -132,7 +133,7 @@ impl Ui {
|
|||||||
ui,
|
ui,
|
||||||
image_map: Map::new(),
|
image_map: Map::new(),
|
||||||
cache: Cache::new(renderer)?,
|
cache: Cache::new(renderer)?,
|
||||||
draw_commands: vec![],
|
draw_commands: Vec::new(),
|
||||||
model: renderer.create_dynamic_model(100)?,
|
model: renderer.create_dynamic_model(100)?,
|
||||||
interface_locals: renderer.create_consts(&[UiLocals::default()])?,
|
interface_locals: renderer.create_consts(&[UiLocals::default()])?,
|
||||||
default_globals: renderer.create_consts(&[Globals::default()])?,
|
default_globals: renderer.create_consts(&[Globals::default()])?,
|
||||||
@ -160,7 +161,18 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_graphic(&mut self, graphic: Graphic) -> image::Id {
|
pub fn add_graphic(&mut self, graphic: Graphic) -> image::Id {
|
||||||
self.image_map.insert(self.cache.add_graphic(graphic))
|
self.image_map
|
||||||
|
.insert((self.cache.add_graphic(graphic), Rotation::None))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_graphic_with_rotations(&mut self, graphic: Graphic) -> img_ids::Rotations {
|
||||||
|
let graphic_id = self.cache.add_graphic(graphic);
|
||||||
|
img_ids::Rotations {
|
||||||
|
none: self.image_map.insert((graphic_id, Rotation::None)),
|
||||||
|
cw90: self.image_map.insert((graphic_id, Rotation::Cw90)),
|
||||||
|
cw180: self.image_map.insert((graphic_id, Rotation::Cw180)),
|
||||||
|
cw270: self.image_map.insert((graphic_id, Rotation::Cw270)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_font(&mut self, font: Arc<Font>) -> font::Id {
|
pub fn new_font(&mut self, font: Arc<Font>) -> font::Id {
|
||||||
@ -257,6 +269,15 @@ impl Ui {
|
|||||||
self.draw_commands.clear();
|
self.draw_commands.clear();
|
||||||
let mut mesh = Mesh::new();
|
let mut mesh = Mesh::new();
|
||||||
|
|
||||||
|
let (half_res, x_align, y_align) = {
|
||||||
|
let res = renderer.get_resolution();
|
||||||
|
(
|
||||||
|
res.map(|e| e as f32 / 2.0),
|
||||||
|
(res.x & 1) as f32 * 0.5,
|
||||||
|
(res.y & 1) as f32 * 0.5,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: this could be removed entirely if the draw call just used both textures,
|
// TODO: this could be removed entirely if the draw call just used both textures,
|
||||||
// however this allows for flexibility if we want to interweave other draw calls later.
|
// however this allows for flexibility if we want to interweave other draw calls later.
|
||||||
enum State {
|
enum State {
|
||||||
@ -372,10 +393,15 @@ impl Ui {
|
|||||||
let vy = |y: f64| (y / ui_win_h * 2.0) as f32;
|
let vy = |y: f64| (y / ui_win_h * 2.0) as f32;
|
||||||
let gl_aabr = |rect: Rect| {
|
let gl_aabr = |rect: Rect| {
|
||||||
let (l, r, b, t) = rect.l_r_b_t();
|
let (l, r, b, t) = rect.l_r_b_t();
|
||||||
Aabr {
|
let min = Vec2::new(
|
||||||
min: Vec2::new(vx(l), vy(b)),
|
((vx(l) * half_res.x + x_align).round() - x_align) / half_res.x,
|
||||||
max: Vec2::new(vx(r), vy(t)),
|
((vy(b) * half_res.y + y_align).round() - y_align) / half_res.y,
|
||||||
}
|
);
|
||||||
|
let max = Vec2::new(
|
||||||
|
((vx(r) * half_res.x + x_align).round() - x_align) / half_res.x,
|
||||||
|
((vy(t) * half_res.y + y_align).round() - y_align) / half_res.y,
|
||||||
|
);
|
||||||
|
Aabr { min, max }
|
||||||
};
|
};
|
||||||
|
|
||||||
match kind {
|
match kind {
|
||||||
@ -384,7 +410,7 @@ impl Ui {
|
|||||||
color,
|
color,
|
||||||
source_rect: _, // TODO: <-- use this
|
source_rect: _, // TODO: <-- use this
|
||||||
} => {
|
} => {
|
||||||
let graphic_id = self
|
let (graphic_id, rotation) = self
|
||||||
.image_map
|
.image_map
|
||||||
.get(&image_id)
|
.get(&image_id)
|
||||||
.expect("Image does not exist in image map");
|
.expect("Image does not exist in image map");
|
||||||
@ -406,9 +432,10 @@ impl Ui {
|
|||||||
let color =
|
let color =
|
||||||
srgba_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa().into());
|
srgba_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa().into());
|
||||||
|
|
||||||
|
let gl_aabr = gl_aabr(rect);
|
||||||
let resolution = Vec2::new(
|
let resolution = Vec2::new(
|
||||||
(rect.w() * p_scale_factor).round() as u16,
|
(gl_aabr.size().w * half_res.x).round() as u16,
|
||||||
(rect.h() * p_scale_factor).round() as u16,
|
(gl_aabr.size().h * half_res.y).round() as u16,
|
||||||
);
|
);
|
||||||
// Transform the source rectangle into uv coordinate.
|
// Transform the source rectangle into uv coordinate.
|
||||||
// TODO: Make sure this is right.
|
// TODO: Make sure this is right.
|
||||||
@ -434,22 +461,26 @@ impl Ui {
|
|||||||
cache_tex.get_dimensions().map(|e| e as f32).into_tuple();
|
cache_tex.get_dimensions().map(|e| e as f32).into_tuple();
|
||||||
|
|
||||||
// Cache graphic at particular resolution.
|
// Cache graphic at particular resolution.
|
||||||
let uv_aabr =
|
let uv_aabr = match graphic_cache.queue_res(
|
||||||
match graphic_cache.queue_res(*graphic_id, resolution, source_aabr) {
|
*graphic_id,
|
||||||
Some(aabr) => Aabr {
|
resolution,
|
||||||
min: Vec2::new(
|
source_aabr,
|
||||||
aabr.min.x as f32 / cache_w,
|
*rotation,
|
||||||
aabr.max.y as f32 / cache_h,
|
) {
|
||||||
),
|
Some(aabr) => Aabr {
|
||||||
max: Vec2::new(
|
min: Vec2::new(
|
||||||
aabr.max.x as f32 / cache_w,
|
(aabr.min.x as f32) / cache_w,
|
||||||
aabr.min.y as f32 / cache_h,
|
(aabr.max.y as f32) / cache_h,
|
||||||
),
|
),
|
||||||
},
|
max: Vec2::new(
|
||||||
None => continue,
|
(aabr.max.x as f32) / cache_w,
|
||||||
};
|
(aabr.min.y as f32) / cache_h,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
mesh.push_quad(create_ui_quad(gl_aabr(rect), uv_aabr, color, UiMode::Image));
|
mesh.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, UiMode::Image));
|
||||||
}
|
}
|
||||||
PrimitiveKind::Text {
|
PrimitiveKind::Text {
|
||||||
color,
|
color,
|
||||||
|
319
voxygen/src/ui/widgets/image_frame.rs
Normal file
319
voxygen/src/ui/widgets/image_frame.rs
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
//! A widget for selecting a single value along some linear range.
|
||||||
|
use conrod_core::{
|
||||||
|
builder_methods, image,
|
||||||
|
widget::{self, Image, Rectangle},
|
||||||
|
widget_ids, Color, Colorable, Positionable, Rect, Sizeable, UiCell, Widget, WidgetCommon,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, WidgetCommon)]
|
||||||
|
pub struct ImageFrame {
|
||||||
|
#[conrod(common_builder)]
|
||||||
|
common: widget::CommonBuilder,
|
||||||
|
// Edge images [t, b, r, l]
|
||||||
|
edges: [image::Id; 4],
|
||||||
|
edge_src_rects: [Option<Rect>; 4],
|
||||||
|
// Corner images [tr, tl, br, bl]
|
||||||
|
corners: [image::Id; 4],
|
||||||
|
corner_src_rects: [Option<Rect>; 4],
|
||||||
|
// Center
|
||||||
|
center: Center,
|
||||||
|
// Thickness of the frame border, determines the size used for the edge and corner images
|
||||||
|
border_size: BorderSize,
|
||||||
|
// Color to apply to all images making up the image frame
|
||||||
|
color: Option<Color>,
|
||||||
|
// TODO: would it be useful to have an optional close button be a part of this?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Center {
|
||||||
|
Plain(Color),
|
||||||
|
Image(image::Id, Option<Rect>),
|
||||||
|
}
|
||||||
|
impl From<Color> for Center {
|
||||||
|
fn from(color: Color) -> Self {
|
||||||
|
Center::Plain(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<image::Id> for Center {
|
||||||
|
fn from(image: image::Id) -> Self {
|
||||||
|
Center::Image(image, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<(image::Id, Rect)> for Center {
|
||||||
|
fn from((image, src_rect): (image::Id, Rect)) -> Self {
|
||||||
|
Center::Image(image, Some(src_rect))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BorderSize {
|
||||||
|
top: f64,
|
||||||
|
bottom: f64,
|
||||||
|
right: f64,
|
||||||
|
left: f64,
|
||||||
|
}
|
||||||
|
impl From<f64> for BorderSize {
|
||||||
|
fn from(thickness: f64) -> Self {
|
||||||
|
BorderSize {
|
||||||
|
top: thickness,
|
||||||
|
bottom: thickness,
|
||||||
|
right: thickness,
|
||||||
|
left: thickness,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<[f64; 2]> for BorderSize {
|
||||||
|
fn from([vertical, horizontal]: [f64; 2]) -> Self {
|
||||||
|
BorderSize {
|
||||||
|
top: horizontal,
|
||||||
|
bottom: horizontal,
|
||||||
|
right: vertical,
|
||||||
|
left: vertical,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<[f64; 4]> for BorderSize {
|
||||||
|
fn from(vals: [f64; 4]) -> Self {
|
||||||
|
BorderSize {
|
||||||
|
top: vals[0],
|
||||||
|
bottom: vals[1],
|
||||||
|
right: vals[2],
|
||||||
|
left: vals[3],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
widget_ids! {
|
||||||
|
struct Ids {
|
||||||
|
center_plain,
|
||||||
|
center_image,
|
||||||
|
right,
|
||||||
|
top_right,
|
||||||
|
top,
|
||||||
|
top_left,
|
||||||
|
left,
|
||||||
|
bottom_left,
|
||||||
|
bottom,
|
||||||
|
bottom_right,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the state of the ImageFrame widget.
|
||||||
|
pub struct State {
|
||||||
|
ids: Ids,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageFrame {
|
||||||
|
pub fn new(
|
||||||
|
edges: [image::Id; 4],
|
||||||
|
corners: [image::Id; 4],
|
||||||
|
center: impl Into<Center>,
|
||||||
|
border_size: impl Into<BorderSize>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
common: widget::CommonBuilder::default(),
|
||||||
|
edges,
|
||||||
|
edge_src_rects: [None; 4],
|
||||||
|
corners,
|
||||||
|
corner_src_rects: [None; 4],
|
||||||
|
center: center.into(),
|
||||||
|
border_size: border_size.into(),
|
||||||
|
color: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder_methods! {
|
||||||
|
pub edge_src_rects { edge_src_rects = [Option<Rect>; 4] }
|
||||||
|
pub corner_src_rects { corner_src_rects = [Option<Rect>; 4] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for ImageFrame {
|
||||||
|
type State = State;
|
||||||
|
type Style = ();
|
||||||
|
type Event = ();
|
||||||
|
|
||||||
|
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
|
||||||
|
State {
|
||||||
|
ids: Ids::new(id_gen),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style(&self) -> Self::Style {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the state of the ImageFrame
|
||||||
|
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
|
||||||
|
let widget::UpdateArgs {
|
||||||
|
id,
|
||||||
|
state,
|
||||||
|
rect,
|
||||||
|
ui,
|
||||||
|
..
|
||||||
|
} = args;
|
||||||
|
|
||||||
|
let (frame_w, frame_h) = rect.w_h();
|
||||||
|
|
||||||
|
let t_height = self.border_size.top.min(frame_h);
|
||||||
|
let b_height = self.border_size.bottom.min(frame_h);
|
||||||
|
let r_width = self.border_size.right.min(frame_w);
|
||||||
|
let l_width = self.border_size.left.min(frame_w);
|
||||||
|
let inner_width = (frame_w - r_width - l_width).max(0.0);
|
||||||
|
let inner_height = (frame_h - t_height - b_height).max(0.0);
|
||||||
|
|
||||||
|
let r_rect = Rect::from_xy_dim(
|
||||||
|
[rect.x() + (inner_width + r_width) / 2.0, rect.y()],
|
||||||
|
[r_width, inner_height],
|
||||||
|
);
|
||||||
|
let tr_rect = Rect::from_xy_dim(
|
||||||
|
[
|
||||||
|
rect.x() + (inner_width + r_width) / 2.0,
|
||||||
|
rect.y() + (inner_height + t_height) / 2.0,
|
||||||
|
],
|
||||||
|
[r_width, t_height],
|
||||||
|
);
|
||||||
|
let t_rect = Rect::from_xy_dim(
|
||||||
|
[rect.x(), rect.y() + (inner_height + t_height) / 2.0],
|
||||||
|
[inner_width, t_height],
|
||||||
|
);
|
||||||
|
let tl_rect = Rect::from_xy_dim(
|
||||||
|
[
|
||||||
|
rect.x() - (inner_width + l_width) / 2.0,
|
||||||
|
rect.y() + (inner_height + t_height) / 2.0,
|
||||||
|
],
|
||||||
|
[l_width, t_height],
|
||||||
|
);
|
||||||
|
let l_rect = Rect::from_xy_dim(
|
||||||
|
[rect.x() - (inner_width + l_width) / 2.0, rect.y()],
|
||||||
|
[l_width, inner_height],
|
||||||
|
);
|
||||||
|
let bl_rect = Rect::from_xy_dim(
|
||||||
|
[
|
||||||
|
rect.x() - (inner_width + l_width) / 2.0,
|
||||||
|
rect.y() - (inner_height + b_height) / 2.0,
|
||||||
|
],
|
||||||
|
[l_width, b_height],
|
||||||
|
);
|
||||||
|
let b_rect = Rect::from_xy_dim(
|
||||||
|
[rect.x(), rect.y() - (inner_height + b_height) / 2.0],
|
||||||
|
[inner_width, b_height],
|
||||||
|
);
|
||||||
|
let br_rect = Rect::from_xy_dim(
|
||||||
|
[
|
||||||
|
rect.x() + (inner_width + r_width) / 2.0,
|
||||||
|
rect.y() - (inner_height + b_height) / 2.0,
|
||||||
|
],
|
||||||
|
[r_width, b_height],
|
||||||
|
);
|
||||||
|
|
||||||
|
let maybe_color = self.color;
|
||||||
|
let set_image = |image_id, rect: Rect, maybe_src_rect, widget_id, ui: &mut UiCell| {
|
||||||
|
Image::new(image_id)
|
||||||
|
.xy(rect.xy())
|
||||||
|
.wh(rect.dim())
|
||||||
|
.parent(id)
|
||||||
|
.graphics_for(id)
|
||||||
|
.and_then(maybe_src_rect, |w, r| w.source_rectangle(r))
|
||||||
|
.color(maybe_color)
|
||||||
|
.set(widget_id, ui);
|
||||||
|
};
|
||||||
|
// Right edge
|
||||||
|
set_image(
|
||||||
|
self.edges[2],
|
||||||
|
r_rect,
|
||||||
|
self.edge_src_rects[2],
|
||||||
|
state.ids.right,
|
||||||
|
ui,
|
||||||
|
);
|
||||||
|
// Top-right corner
|
||||||
|
set_image(
|
||||||
|
self.corners[0],
|
||||||
|
tr_rect,
|
||||||
|
self.corner_src_rects[0],
|
||||||
|
state.ids.top_right,
|
||||||
|
ui,
|
||||||
|
);
|
||||||
|
// Top edge
|
||||||
|
set_image(
|
||||||
|
self.edges[0],
|
||||||
|
t_rect,
|
||||||
|
self.edge_src_rects[0],
|
||||||
|
state.ids.top,
|
||||||
|
ui,
|
||||||
|
);
|
||||||
|
// Top-left corner
|
||||||
|
set_image(
|
||||||
|
self.corners[1],
|
||||||
|
tl_rect,
|
||||||
|
self.corner_src_rects[1],
|
||||||
|
state.ids.top_left,
|
||||||
|
ui,
|
||||||
|
);
|
||||||
|
// Left edge
|
||||||
|
set_image(
|
||||||
|
self.edges[3],
|
||||||
|
l_rect,
|
||||||
|
self.edge_src_rects[3],
|
||||||
|
state.ids.left,
|
||||||
|
ui,
|
||||||
|
);
|
||||||
|
// Bottom-left corner
|
||||||
|
set_image(
|
||||||
|
self.corners[3],
|
||||||
|
bl_rect,
|
||||||
|
self.corner_src_rects[3],
|
||||||
|
state.ids.bottom_left,
|
||||||
|
ui,
|
||||||
|
);
|
||||||
|
// Bottom edge
|
||||||
|
set_image(
|
||||||
|
self.edges[1],
|
||||||
|
b_rect,
|
||||||
|
self.edge_src_rects[1],
|
||||||
|
state.ids.bottom,
|
||||||
|
ui,
|
||||||
|
);
|
||||||
|
// Bottom-right corner
|
||||||
|
set_image(
|
||||||
|
self.corners[2],
|
||||||
|
br_rect,
|
||||||
|
self.corner_src_rects[2],
|
||||||
|
state.ids.bottom_right,
|
||||||
|
ui,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Center,
|
||||||
|
match self.center {
|
||||||
|
Center::Plain(color) => {
|
||||||
|
Rectangle::fill_with([inner_width, inner_height], color)
|
||||||
|
.xy(rect.xy())
|
||||||
|
.parent(id)
|
||||||
|
.graphics_for(id)
|
||||||
|
.and_then(maybe_color, |w, c| {
|
||||||
|
w.color(color.alpha(match c {
|
||||||
|
Color::Rgba(_, _, _, a) => a,
|
||||||
|
Color::Hsla(_, _, _, a) => a,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.set(state.ids.center_plain, ui);
|
||||||
|
}
|
||||||
|
Center::Image(image_id, maybe_src_rect) => {
|
||||||
|
set_image(
|
||||||
|
image_id,
|
||||||
|
Rect::from_xy_dim(rect.xy(), [inner_width, inner_height]),
|
||||||
|
maybe_src_rect,
|
||||||
|
state.ids.center_image,
|
||||||
|
ui,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Colorable for ImageFrame {
|
||||||
|
fn color(mut self, color: Color) -> Self {
|
||||||
|
self.color = Some(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
@ -67,7 +67,7 @@ widget_ids! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the state of the Slider widget.
|
/// Represents the state of the ImageSlider widget.
|
||||||
pub struct State {
|
pub struct State {
|
||||||
ids: Ids,
|
ids: Ids,
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
pub mod image_frame;
|
||||||
pub mod image_slider;
|
pub mod image_slider;
|
||||||
pub mod ingame;
|
pub mod ingame;
|
||||||
pub mod toggle_button;
|
pub mod toggle_button;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
use super::image_frame::ImageFrame;
|
||||||
use conrod_core::{
|
use conrod_core::{
|
||||||
builder_method, builder_methods, input::global::Global, text, widget, widget_ids, Color,
|
builder_method, builder_methods, image, input::global::Global, position::Dimension, text,
|
||||||
Colorable, FontSize, Positionable, Sizeable, UiCell, Widget, WidgetCommon, WidgetStyle,
|
widget, widget_ids, Color, Colorable, FontSize, Positionable, Sizeable, Ui, UiCell, Widget,
|
||||||
|
WidgetCommon, WidgetStyle,
|
||||||
};
|
};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
@ -87,11 +89,28 @@ impl TooltipManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn set_tooltip(&mut self, tooltip: Tooltip, src_id: widget::Id, ui: &mut UiCell) {
|
fn set_tooltip(
|
||||||
|
&mut self,
|
||||||
|
tooltip: &Tooltip,
|
||||||
|
title_text: &str,
|
||||||
|
desc_text: &str,
|
||||||
|
img_id: Option<image::Id>,
|
||||||
|
image_dims: Option<(f64, f64)>,
|
||||||
|
src_id: widget::Id,
|
||||||
|
ui: &mut UiCell,
|
||||||
|
) {
|
||||||
let tooltip_id = self.tooltip_id;
|
let tooltip_id = self.tooltip_id;
|
||||||
let mp_h = MOUSE_PAD_Y / self.logical_scale_factor;
|
let mp_h = MOUSE_PAD_Y / self.logical_scale_factor;
|
||||||
|
|
||||||
let tooltip = |transparency, mouse_pos: [f64; 2], ui: &mut UiCell| {
|
let tooltip = |transparency, mouse_pos: [f64; 2], ui: &mut UiCell| {
|
||||||
|
// Fill in text and the potential image beforehand to get an accurate size for spacing
|
||||||
|
let tooltip = tooltip
|
||||||
|
.clone()
|
||||||
|
.title(title_text)
|
||||||
|
.desc(desc_text)
|
||||||
|
.image(img_id)
|
||||||
|
.image_dims(image_dims);
|
||||||
|
|
||||||
let [t_w, t_h] = tooltip.get_wh(ui).unwrap_or([0.0, 0.0]);
|
let [t_w, t_h] = tooltip.get_wh(ui).unwrap_or([0.0, 0.0]);
|
||||||
let [m_x, m_y] = mouse_pos;
|
let [m_x, m_y] = mouse_pos;
|
||||||
let (w_w, w_h) = (ui.win_w, ui.win_h);
|
let (w_w, w_h) = (ui.win_w, ui.win_h);
|
||||||
@ -128,12 +147,32 @@ impl TooltipManager {
|
|||||||
pub struct Tooltipped<'a, W> {
|
pub struct Tooltipped<'a, W> {
|
||||||
inner: W,
|
inner: W,
|
||||||
tooltip_manager: &'a mut TooltipManager,
|
tooltip_manager: &'a mut TooltipManager,
|
||||||
tooltip: Tooltip<'a>,
|
title_text: &'a str,
|
||||||
|
desc_text: &'a str,
|
||||||
|
img_id: Option<image::Id>,
|
||||||
|
image_dims: Option<(f64, f64)>,
|
||||||
|
tooltip: &'a Tooltip<'a>,
|
||||||
}
|
}
|
||||||
impl<'a, W: Widget> Tooltipped<'a, W> {
|
impl<'a, W: Widget> Tooltipped<'a, W> {
|
||||||
|
pub fn tooltip_image(mut self, img_id: image::Id) -> Self {
|
||||||
|
self.img_id = Some(img_id);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn tooltip_image_dims(mut self, dims: (f64, f64)) -> Self {
|
||||||
|
self.image_dims = Some(dims);
|
||||||
|
self
|
||||||
|
}
|
||||||
pub fn set(self, id: widget::Id, ui: &mut UiCell) -> W::Event {
|
pub fn set(self, id: widget::Id, ui: &mut UiCell) -> W::Event {
|
||||||
let event = self.inner.set(id, ui);
|
let event = self.inner.set(id, ui);
|
||||||
self.tooltip_manager.set_tooltip(self.tooltip, id, ui);
|
self.tooltip_manager.set_tooltip(
|
||||||
|
self.tooltip,
|
||||||
|
self.title_text,
|
||||||
|
self.desc_text,
|
||||||
|
self.img_id,
|
||||||
|
self.image_dims,
|
||||||
|
id,
|
||||||
|
ui,
|
||||||
|
);
|
||||||
event
|
event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,7 +182,9 @@ pub trait Tooltipable {
|
|||||||
fn with_tooltip<'a>(
|
fn with_tooltip<'a>(
|
||||||
self,
|
self,
|
||||||
tooltip_manager: &'a mut TooltipManager,
|
tooltip_manager: &'a mut TooltipManager,
|
||||||
tooltip: Tooltip<'a>,
|
title_text: &'a str,
|
||||||
|
desc_text: &'a str,
|
||||||
|
tooltip: &'a Tooltip<'a>,
|
||||||
) -> Tooltipped<'a, Self>
|
) -> Tooltipped<'a, Self>
|
||||||
where
|
where
|
||||||
Self: std::marker::Sized;
|
Self: std::marker::Sized;
|
||||||
@ -152,16 +193,33 @@ impl<W: Widget> Tooltipable for W {
|
|||||||
fn with_tooltip<'a>(
|
fn with_tooltip<'a>(
|
||||||
self,
|
self,
|
||||||
tooltip_manager: &'a mut TooltipManager,
|
tooltip_manager: &'a mut TooltipManager,
|
||||||
tooltip: Tooltip<'a>,
|
title_text: &'a str,
|
||||||
|
desc_text: &'a str,
|
||||||
|
tooltip: &'a Tooltip<'a>,
|
||||||
) -> Tooltipped<'a, W> {
|
) -> Tooltipped<'a, W> {
|
||||||
Tooltipped {
|
Tooltipped {
|
||||||
inner: self,
|
inner: self,
|
||||||
tooltip_manager,
|
tooltip_manager,
|
||||||
|
title_text,
|
||||||
|
desc_text,
|
||||||
|
img_id: None,
|
||||||
|
image_dims: None,
|
||||||
tooltip,
|
tooltip,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Vertical spacing between elements of the tooltip
|
||||||
|
const V_PAD: f64 = 10.0;
|
||||||
|
/// Horizontal spacing between elements of the tooltip
|
||||||
|
const H_PAD: f64 = 10.0;
|
||||||
|
/// Default portion of inner width that goes to an image
|
||||||
|
const IMAGE_W_FRAC: f64 = 0.3;
|
||||||
|
/// Default width multiplied by the description font size
|
||||||
|
const DEFAULT_CHAR_W: f64 = 30.0;
|
||||||
|
/// Text vertical spacing factor to account for overhanging text
|
||||||
|
const TEXT_SPACE_FACTOR: f64 = 0.35;
|
||||||
|
|
||||||
/// A widget for displaying tooltips
|
/// A widget for displaying tooltips
|
||||||
#[derive(Clone, WidgetCommon)]
|
#[derive(Clone, WidgetCommon)]
|
||||||
pub struct Tooltip<'a> {
|
pub struct Tooltip<'a> {
|
||||||
@ -169,13 +227,16 @@ pub struct Tooltip<'a> {
|
|||||||
common: widget::CommonBuilder,
|
common: widget::CommonBuilder,
|
||||||
title_text: &'a str,
|
title_text: &'a str,
|
||||||
desc_text: &'a str,
|
desc_text: &'a str,
|
||||||
|
image: Option<image::Id>,
|
||||||
|
image_dims: Option<(f64, f64)>,
|
||||||
style: Style,
|
style: Style,
|
||||||
transparency: f32,
|
transparency: f32,
|
||||||
|
image_frame: ImageFrame,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, WidgetStyle)]
|
#[derive(Clone, Debug, Default, PartialEq, WidgetStyle)]
|
||||||
pub struct Style {
|
pub struct Style {
|
||||||
#[conrod(default = "theme.background_color")]
|
#[conrod(default = "Color::Rgba(1.0, 1.0, 1.0, 1.0)")]
|
||||||
pub color: Option<Color>,
|
pub color: Option<Color>,
|
||||||
title: widget::text::Style,
|
title: widget::text::Style,
|
||||||
desc: widget::text::Style,
|
desc: widget::text::Style,
|
||||||
@ -186,7 +247,8 @@ widget_ids! {
|
|||||||
struct Ids {
|
struct Ids {
|
||||||
title,
|
title,
|
||||||
desc,
|
desc,
|
||||||
back_rect,
|
image_frame,
|
||||||
|
image,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,13 +257,16 @@ pub struct State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Tooltip<'a> {
|
impl<'a> Tooltip<'a> {
|
||||||
pub fn new(title: &'a str, desc: &'a str) -> Self {
|
pub fn new(image_frame: ImageFrame) -> Self {
|
||||||
Tooltip {
|
Tooltip {
|
||||||
common: widget::CommonBuilder::default(),
|
common: widget::CommonBuilder::default(),
|
||||||
style: Style::default(),
|
style: Style::default(),
|
||||||
title_text: title,
|
title_text: "",
|
||||||
desc_text: desc,
|
desc_text: "",
|
||||||
transparency: 1.0,
|
transparency: 1.0,
|
||||||
|
image_frame,
|
||||||
|
image: None,
|
||||||
|
image_dims: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +285,28 @@ impl<'a> Tooltip<'a> {
|
|||||||
// self.justify(text::Justify::Right)
|
// self.justify(text::Justify::Right)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// TODO: add method(s) to make children widgets and use that to determine height in height function (and in update to draw the widgets)
|
fn text_image_width(&self, total_width: f64) -> (f64, f64) {
|
||||||
|
let inner_width = (total_width - H_PAD * 2.0).max(0.0);
|
||||||
|
// Image defaults to 30% of the width
|
||||||
|
let image_w = if self.image.is_some() {
|
||||||
|
match self.image_dims {
|
||||||
|
Some((w, _)) => w,
|
||||||
|
None => (inner_width - H_PAD).max(0.0) * IMAGE_W_FRAC,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
// Text gets the remaining width
|
||||||
|
let text_w = (inner_width
|
||||||
|
- if self.image.is_some() {
|
||||||
|
image_w + H_PAD
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
})
|
||||||
|
.max(0.0);
|
||||||
|
|
||||||
|
(text_w, image_w)
|
||||||
|
}
|
||||||
|
|
||||||
/// Specify the font used for displaying the text.
|
/// Specify the font used for displaying the text.
|
||||||
pub fn font_id(mut self, font_id: text::font::Id) -> Self {
|
pub fn font_id(mut self, font_id: text::font::Id) -> Self {
|
||||||
@ -236,6 +322,10 @@ impl<'a> Tooltip<'a> {
|
|||||||
pub desc_font_size { style.desc.font_size = Some(FontSize) }
|
pub desc_font_size { style.desc.font_size = Some(FontSize) }
|
||||||
pub title_justify { style.title.justify = Some(text::Justify) }
|
pub title_justify { style.title.justify = Some(text::Justify) }
|
||||||
pub desc_justify { style.desc.justify = Some(text::Justify) }
|
pub desc_justify { style.desc.justify = Some(text::Justify) }
|
||||||
|
image { image = Option<image::Id> }
|
||||||
|
title { title_text = &'a str }
|
||||||
|
desc { desc_text = &'a str }
|
||||||
|
image_dims { image_dims = Option<(f64, f64)> }
|
||||||
transparency { transparency = f32 }
|
transparency { transparency = f32 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -265,41 +355,132 @@ impl<'a> Widget for Tooltip<'a> {
|
|||||||
..
|
..
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
|
// Widths
|
||||||
|
let (text_w, image_w) = self.text_image_width(rect.w());
|
||||||
|
|
||||||
// Apply transparency
|
// Apply transparency
|
||||||
let color = style.color(ui.theme()).alpha(self.transparency);
|
let color = style.color(ui.theme()).alpha(self.transparency);
|
||||||
|
|
||||||
// Background rectangle
|
// Background image frame
|
||||||
widget::Rectangle::fill(rect.dim())
|
self.image_frame
|
||||||
|
.wh(rect.dim())
|
||||||
.xy(rect.xy())
|
.xy(rect.xy())
|
||||||
.graphics_for(id)
|
.graphics_for(id)
|
||||||
.parent(id)
|
.parent(id)
|
||||||
.color(color)
|
.color(color)
|
||||||
//.floating(true)
|
.set(state.ids.image_frame, ui);
|
||||||
.set(state.ids.back_rect, ui);
|
|
||||||
|
// Image
|
||||||
|
if let Some(img_id) = self.image {
|
||||||
|
widget::Image::new(img_id)
|
||||||
|
.w_h(image_w, self.image_dims.map_or(image_w, |(_, h)| h))
|
||||||
|
.graphics_for(id)
|
||||||
|
.parent(id)
|
||||||
|
.color(Some(color))
|
||||||
|
.top_left_with_margins_on(state.ids.image_frame, V_PAD, H_PAD)
|
||||||
|
.set(state.ids.image, ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spacing for overhanging text
|
||||||
|
let title_space = self.style.title.font_size(&ui.theme) as f64 * TEXT_SPACE_FACTOR;
|
||||||
|
|
||||||
// Title of tooltip
|
// Title of tooltip
|
||||||
widget::Text::new(self.title_text)
|
if !self.title_text.is_empty() {
|
||||||
.w(rect.w())
|
let title = widget::Text::new(self.title_text)
|
||||||
.graphics_for(id)
|
.w(text_w)
|
||||||
.parent(id)
|
.graphics_for(id)
|
||||||
.top_left_with_margins_on(state.ids.back_rect, 5.0, 5.0)
|
.parent(id)
|
||||||
.with_style(self.style.title)
|
.with_style(self.style.title)
|
||||||
// Apply transparency
|
// Apply transparency
|
||||||
.color(style.title.color(ui.theme()).alpha(self.transparency))
|
.color(style.title.color(ui.theme()).alpha(self.transparency));
|
||||||
//.floating(true)
|
|
||||||
|
if self.image.is_some() {
|
||||||
|
title
|
||||||
|
.right_from(state.ids.image, H_PAD)
|
||||||
|
.align_top_of(state.ids.image)
|
||||||
|
} else {
|
||||||
|
title.top_left_with_margins_on(state.ids.image_frame, V_PAD, H_PAD)
|
||||||
|
}
|
||||||
.set(state.ids.title, ui);
|
.set(state.ids.title, ui);
|
||||||
|
}
|
||||||
|
|
||||||
// Description of tooltip
|
// Description of tooltip
|
||||||
widget::Text::new(self.desc_text)
|
let desc = widget::Text::new(self.desc_text)
|
||||||
.w(rect.w())
|
.w(text_w)
|
||||||
.graphics_for(id)
|
.graphics_for(id)
|
||||||
.parent(id)
|
.parent(id)
|
||||||
.down_from(state.ids.title, 10.0)
|
|
||||||
.with_style(self.style.desc)
|
|
||||||
// Apply transparency
|
// Apply transparency
|
||||||
.color(style.desc.color(ui.theme()).alpha(self.transparency))
|
.color(style.desc.color(ui.theme()).alpha(self.transparency))
|
||||||
// .floating(true)
|
.with_style(self.style.desc);
|
||||||
.set(state.ids.desc, ui);
|
|
||||||
|
if !self.title_text.is_empty() {
|
||||||
|
desc.down_from(state.ids.title, V_PAD * 0.5 + title_space)
|
||||||
|
.align_left_of(state.ids.title)
|
||||||
|
} else {
|
||||||
|
if self.image.is_some() {
|
||||||
|
desc.right_from(state.ids.image, H_PAD)
|
||||||
|
.align_top_of(state.ids.image)
|
||||||
|
} else {
|
||||||
|
desc.top_left_with_margins_on(state.ids.image_frame, V_PAD, H_PAD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.set(state.ids.desc, ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default width is based on the description font size unless the text is small enough to fit on a single line
|
||||||
|
fn default_x_dimension(&self, ui: &Ui) -> Dimension {
|
||||||
|
let single_line_title_w = widget::Text::new(self.title_text)
|
||||||
|
.with_style(self.style.title)
|
||||||
|
.get_w(ui)
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
let single_line_desc_w = widget::Text::new(self.desc_text)
|
||||||
|
.with_style(self.style.desc)
|
||||||
|
.get_w(ui)
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
|
||||||
|
let text_w = single_line_title_w.max(single_line_desc_w);
|
||||||
|
let inner_w = if self.image.is_some() {
|
||||||
|
match self.image_dims {
|
||||||
|
Some((w, _)) => w + text_w + H_PAD,
|
||||||
|
None => text_w / (1.0 - IMAGE_W_FRAC) + H_PAD,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text_w
|
||||||
|
};
|
||||||
|
|
||||||
|
let width =
|
||||||
|
inner_w.min(self.style.desc.font_size(&ui.theme) as f64 * DEFAULT_CHAR_W) + 2.0 * H_PAD;
|
||||||
|
Dimension::Absolute(width)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_y_dimension(&self, ui: &Ui) -> Dimension {
|
||||||
|
let (text_w, image_w) = self.text_image_width(self.get_w(ui).unwrap_or(0.0));
|
||||||
|
let title_h = if self.title_text.is_empty() {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
widget::Text::new(self.title_text)
|
||||||
|
.with_style(self.style.title)
|
||||||
|
.w(text_w)
|
||||||
|
.get_h(ui)
|
||||||
|
.unwrap_or(0.0)
|
||||||
|
+ self.style.title.font_size(&ui.theme) as f64 * TEXT_SPACE_FACTOR
|
||||||
|
+ 0.5 * V_PAD
|
||||||
|
};
|
||||||
|
let desc_h = if self.desc_text.is_empty() {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
widget::Text::new(self.desc_text)
|
||||||
|
.with_style(self.style.desc)
|
||||||
|
.w(text_w)
|
||||||
|
.get_h(ui)
|
||||||
|
.unwrap_or(0.0)
|
||||||
|
+ self.style.desc.font_size(&ui.theme) as f64 * TEXT_SPACE_FACTOR
|
||||||
|
};
|
||||||
|
// Image defaults to square shape
|
||||||
|
let image_h = self.image_dims.map_or(image_w, |(_, h)| h);
|
||||||
|
// Title height + desc height + padding/spacing
|
||||||
|
let height = (title_h + desc_h).max(image_h) + 2.0 * V_PAD;
|
||||||
|
Dimension::Absolute(height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user