Convert tooltips to use ImageFrame, add autosizing

This commit is contained in:
Imbris 2019-09-01 13:28:52 -04:00
parent e9bedf529d
commit 3ccbb0f38d
4 changed files with 157 additions and 51 deletions

View File

@ -56,7 +56,7 @@ pub fn rgb_to_hsv(rgb: Rgb<f32>) -> Vec3<f32> {
let h = if max == min {
0.0
} else {
let mut h = (60.0 * (add + diff / (max - min)));
let mut h = 60.0 * (add + diff / (max - min));
if h < 0.0 {
h += 360.0;
}

View File

@ -58,11 +58,6 @@ widget_ids! {
error_frame,
button_ok,
version,
// TODO remove
frame_test,
test2,
}
}
@ -90,8 +85,8 @@ rotation_image_ids! {
<VoxelGraphic>
// Tooltip Test
tt_side: "voxygen/element/frames/tt_test_edge.vox",
tt_corner: "voxygen/element/frames/tt_test_corner_tr.vox",
tt_side: "voxygen/element/frames/tt_test_edge",
tt_corner: "voxygen/element/frames/tt_test_corner_tr",
}
}
@ -176,26 +171,6 @@ impl MainMenuUi {
.top_right_with_margins(30.0, 30.0)
.set(self.ids.v_logo, ui_widgets);
Image::new(self.rot_imgs.tt_side.cw90)
.w_h(50.0, 50.0)
.top_left_with_margins_on(ui_widgets.window, 25.0, 25.0)
.set(self.ids.test2, ui_widgets);
// TODO: remove me
// Test image frame
// 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.8, 0.7, 0.4, 1.0),
30.0,
)
.w_h(250.0, 200.0)
.down(5.0)
.set(self.ids.frame_test, ui_widgets);
Text::new(version)
.top_right_with_margins_on(ui_widgets.window, 5.0, 5.0)
.font_size(14)
@ -505,11 +480,23 @@ impl MainMenuUi {
.label_y(Relative::Scalar(5.0))
.with_tooltip(
tooltip_manager,
Tooltip::new("Login", "Click to login with the entered details")
.title_font_size(15)
.desc_font_size(10)
.title_text_color(TEXT_COLOR)
.desc_text_color(TEXT_COLOR_2),
// TODO improve this interface
Tooltip::new("Login", "Click to login with the entered details", &{
// 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(60)
.desc_font_size(10)
.title_text_color(TEXT_COLOR)
.desc_text_color(TEXT_COLOR_2),
)
.set(self.ids.login_button, ui_widgets)
.was_clicked()

View File

@ -12,7 +12,7 @@ use conrod_core::{
///
/// Its reaction is triggered if the value is updated or if the mouse button is released while
/// the cursor is above the rectangle.
#[derive(WidgetCommon)]
#[derive(Clone, WidgetCommon)]
pub struct ImageFrame {
#[conrod(common_builder)]
common: widget::CommonBuilder,
@ -32,6 +32,7 @@ pub struct ImageFrame {
// 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>),
@ -52,6 +53,7 @@ impl From<(image::Id, Rect)> for Center {
}
}
#[derive(Clone)]
pub struct BorderSize {
top: f64,
bottom: f64,
@ -296,7 +298,12 @@ impl Widget for ImageFrame {
.xy(rect.xy())
.parent(id)
.graphics_for(id)
.and_then(maybe_color, |w, c| w.color(c))
.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) => {

View File

@ -1,6 +1,8 @@
use super::image_frame::ImageFrame;
use conrod_core::{
builder_method, builder_methods, input::global::Global, text, widget, widget_ids, Color,
Colorable, FontSize, Positionable, Sizeable, UiCell, Widget, WidgetCommon, WidgetStyle,
builder_method, builder_methods, image, input::global::Global, position::Dimension, text,
widget, widget_ids, Color, Colorable, FontSize, Positionable, Sizeable, Ui, UiCell, Widget,
WidgetCommon, WidgetStyle,
};
use std::time::{Duration, Instant};
@ -162,6 +164,15 @@ impl<W: Widget> Tooltipable for W {
}
}
/// 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;
/// A widget for displaying tooltips
#[derive(Clone, WidgetCommon)]
pub struct Tooltip<'a> {
@ -169,13 +180,16 @@ pub struct Tooltip<'a> {
common: widget::CommonBuilder,
title_text: &'a str,
desc_text: &'a str,
image: Option<image::Id>,
image_dims: Option<(f64, f64)>,
style: Style,
transparency: f32,
image_frame: &'a ImageFrame,
}
#[derive(Clone, Debug, Default, PartialEq, WidgetStyle)]
pub struct Style {
#[conrod(default = "theme.background_color")]
#[conrod(default = "Color::Rgba(1.0, 1.0, 1.0, 1.0)")]
pub color: Option<Color>,
title: widget::text::Style,
desc: widget::text::Style,
@ -186,7 +200,8 @@ widget_ids! {
struct Ids {
title,
desc,
back_rect,
image_frame,
image,
}
}
@ -195,13 +210,16 @@ pub struct State {
}
impl<'a> Tooltip<'a> {
pub fn new(title: &'a str, desc: &'a str) -> Self {
pub fn new(title: &'a str, desc: &'a str, image_frame: &'a ImageFrame) -> Self {
Tooltip {
common: widget::CommonBuilder::default(),
style: Style::default(),
title_text: title,
desc_text: desc,
transparency: 1.0,
image_frame,
image: None,
image_dims: None,
}
}
@ -220,7 +238,29 @@ impl<'a> Tooltip<'a> {
// 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 title_desc_image_width(&self, total_width: f64) -> (f64, f64, f64) {
let inner_width = (total_width - H_PAD * 2.0).max(0.0);
let title_w = inner_width;
// 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
};
// Description gets the remaining width
let desc_w = (inner_width
- if self.image.is_some() {
image_w + H_PAD
} else {
0.0
})
.max(0.0);
(title_w, desc_w, image_w)
}
/// Specify the font used for displaying the text.
pub fn font_id(mut self, font_id: text::font::Id) -> Self {
@ -236,6 +276,8 @@ impl<'a> Tooltip<'a> {
pub desc_font_size { style.desc.font_size = Some(FontSize) }
pub title_justify { style.title.justify = Some(text::Justify) }
pub desc_justify { style.desc.justify = Some(text::Justify) }
pub image { image = Some(image::Id) }
pub image_dims { image_dims = Some((f64, f64)) }
transparency { transparency = f32 }
}
}
@ -268,35 +310,105 @@ impl<'a> Widget for Tooltip<'a> {
// Apply transparency
let color = style.color(ui.theme()).alpha(self.transparency);
// Background rectangle
widget::Rectangle::fill(rect.dim())
// Background image frame
self.image_frame
.clone()
.wh(rect.dim())
.xy(rect.xy())
.graphics_for(id)
.parent(id)
.color(color)
.set(state.ids.back_rect, ui);
.set(state.ids.image_frame, ui);
// Widths
let (title_w, desc_w, image_w) = self.title_desc_image_width(rect.w());
// Title of tooltip
widget::Text::new(self.title_text)
.w(rect.w())
.w(title_w)
.graphics_for(id)
.parent(id)
.top_left_with_margins_on(state.ids.back_rect, 5.0, 5.0)
.top_left_with_margins_on(state.ids.image_frame, V_PAD, H_PAD)
.with_style(self.style.title)
// Apply transparency
.color(style.title.color(ui.theme()).alpha(self.transparency))
.set(state.ids.title, 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)
.down_from(state.ids.title, V_PAD)
.align_left_of(state.ids.title)
.color(Some(color))
.set(state.ids.image, ui);
}
// Description of tooltip
widget::Text::new(self.desc_text)
.w(rect.w())
let desc = widget::Text::new(self.desc_text)
.w(desc_w)
.graphics_for(id)
.parent(id)
.down_from(state.ids.title, 10.0)
.with_style(self.style.desc)
// Apply transparency
.color(style.desc.color(ui.theme()).alpha(self.transparency))
.set(state.ids.desc, ui);
.with_style(self.style.desc);
if self.image.is_some() {
desc.right_from(state.ids.image, H_PAD)
.align_top_of(state.ids.image)
} else {
desc.down_from(state.ids.title, V_PAD)
.align_left_of(state.ids.title)
}
.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 body_w = if self.image.is_some() {
match self.image_dims {
Some((w, _)) => w + single_line_desc_w + H_PAD,
None => single_line_desc_w / (1.0 - IMAGE_W_FRAC) + H_PAD,
}
} else {
single_line_desc_w
};
let width = single_line_title_w
.max(body_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 (title_w, desc_w, image_w) = self.title_desc_image_width(self.get_w(ui).unwrap_or(0.0));
let title_h = widget::Text::new(self.title_text)
.with_style(self.style.title)
.w(title_w)
.get_h(ui)
.unwrap_or(0.0);
let desc_h = widget::Text::new(self.desc_text)
.with_style(self.style.desc)
.w(desc_w)
.get_h(ui)
.unwrap_or(0.0);
// Image defaults to square shape
let body_h = desc_h.max(self.image_dims.map_or(image_w, |(_, h)| h));
// Title height + body height + padding/spacing
// TODO: add spacing dependent on text size
let height = title_h + body_h + 3.0 * V_PAD;
Dimension::Absolute(height)
}
}