From 2c84be4306c78f43cdff24804d8b71d646bdbfb6 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 1 Sep 2019 13:28:52 -0400 Subject: [PATCH] Convert tooltips to use ImageFrame, add autosizing --- common/src/util/mod.rs | 2 +- voxygen/src/menu/main/ui.rs | 51 ++++----- voxygen/src/ui/widgets/image_frame.rs | 11 +- voxygen/src/ui/widgets/tooltip.rs | 144 +++++++++++++++++++++++--- 4 files changed, 157 insertions(+), 51 deletions(-) diff --git a/common/src/util/mod.rs b/common/src/util/mod.rs index 0b98ca0f04..76121b4fea 100644 --- a/common/src/util/mod.rs +++ b/common/src/util/mod.rs @@ -56,7 +56,7 @@ pub fn rgb_to_hsv(rgb: Rgb) -> Vec3 { 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; } diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index 363012ba75..b58fb50a9e 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -58,11 +58,6 @@ widget_ids! { error_frame, button_ok, version, - - - // TODO remove - frame_test, - test2, } } @@ -90,8 +85,8 @@ rotation_image_ids! { // 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() diff --git a/voxygen/src/ui/widgets/image_frame.rs b/voxygen/src/ui/widgets/image_frame.rs index ef24a87e0e..ee63f76bc5 100644 --- a/voxygen/src/ui/widgets/image_frame.rs +++ b/voxygen/src/ui/widgets/image_frame.rs @@ -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), @@ -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) => { diff --git a/voxygen/src/ui/widgets/tooltip.rs b/voxygen/src/ui/widgets/tooltip.rs index 72846d6bed..3bff956ce5 100644 --- a/voxygen/src/ui/widgets/tooltip.rs +++ b/voxygen/src/ui/widgets/tooltip.rs @@ -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 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_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, 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) } }