diff --git a/assets/voxygen/element/frames/tooltip/corner.png b/assets/voxygen/element/frames/tooltip/corner.png new file mode 100644 index 0000000000..fff6903304 Binary files /dev/null and b/assets/voxygen/element/frames/tooltip/corner.png differ diff --git a/assets/voxygen/element/frames/tooltip/edge.png b/assets/voxygen/element/frames/tooltip/edge.png new file mode 100644 index 0000000000..777867a960 Binary files /dev/null and b/assets/voxygen/element/frames/tooltip/edge.png differ diff --git a/voxygen/src/menu/char_selection/ui/mod.rs b/voxygen/src/menu/char_selection/ui/mod.rs index 2b0246efa9..d1fd907d7c 100644 --- a/voxygen/src/menu/char_selection/ui/mod.rs +++ b/voxygen/src/menu/char_selection/ui/mod.rs @@ -42,8 +42,8 @@ pub const DISABLED_TEXT_COLOR: iced::Color = iced::Color::from_rgba(1.0, 1.0, 1. pub const TOOLTIP_BACK_COLOR: Rgba = Rgba::new(20, 18, 10, 255); const FILL_FRAC_ONE: f32 = 0.77; const FILL_FRAC_TWO: f32 = 0.60; -const TOOLTIP_HOVER_DUR: std::time::Duration = std::time::Duration::from_millis(500); -const TOOLTIP_FADE_DUR: std::time::Duration = std::time::Duration::from_millis(500); +const TOOLTIP_HOVER_DUR: std::time::Duration = std::time::Duration::from_millis(300); +const TOOLTIP_FADE_DUR: std::time::Duration = std::time::Duration::from_millis(400); const STARTER_HAMMER: &str = "common.items.weapons.hammer.starter_hammer"; const STARTER_BOW: &str = "common.items.weapons.bow.starter_bow"; @@ -112,8 +112,8 @@ image_ids_ice! { button_press: "voxygen.element.buttons.button_press", // Tooltips - tt_edge: "voxygen/element/frames/tt_test_edge", - tt_corner: "voxygen/element/frames/tt_test_corner_tr", + tt_edge: "voxygen/element/frames/tooltip/edge", + tt_corner: "voxygen/element/frames/tooltip/corner", } } @@ -304,7 +304,8 @@ impl Controls { imgs.tt_edge, ), text_color: TEXT_COLOR, - text_size: self.fonts.cyri.scale(15), + text_size: self.fonts.cyri.scale(17), + padding: 10, }; let version = iced::Text::new(&self.version) @@ -384,7 +385,7 @@ impl Controls { .map(|(i, (character, (select_button, delete_button)))| { Overlay::new( Button::new( - select_button, + delete_button, Space::new(Length::Units(16), Length::Units(16)), ) .style( @@ -392,10 +393,19 @@ impl Controls { .hover_image(imgs.delete_button_hover) .press_image(imgs.delete_button_press), ) - .on_press(Message::Delete(i)), + .on_press(Message::Delete(i)) + .with_tooltip( + tooltip_manager, + move || { + tooltip::text( + i18n.get("char_selection.delete_permanently"), + tooltip_style, + ) + }, + ), AspectRatioContainer::new( Button::new( - delete_button, + select_button, Column::with_children(vec![ Text::new(&character.character.alias).into(), // TODO: only construct string once when characters are @@ -411,7 +421,7 @@ impl Controls { .into(), ]), ) - .padding(8) + .padding(10) .style( style::button::Style::new(imgs.selection) .hover_image(imgs.selection_hover) @@ -419,16 +429,7 @@ impl Controls { ) .width(Length::Fill) .height(Length::Fill) - .on_press(Message::Select(i)) - .with_tooltip( - tooltip_manager, - move || { - tooltip::text( - i18n.get("char_selection.delete_permanently"), - tooltip_style, - ) - }, - ), + .on_press(Message::Select(i)), ) .ratio_of_image(imgs.selection), ) diff --git a/voxygen/src/ui/ice/component/tooltip.rs b/voxygen/src/ui/ice/component/tooltip.rs index be27c83959..c53c693095 100644 --- a/voxygen/src/ui/ice/component/tooltip.rs +++ b/voxygen/src/ui/ice/component/tooltip.rs @@ -11,6 +11,7 @@ pub struct Style { pub container: style::container::Style, pub text_color: iced::Color, pub text_size: u16, + pub padding: u16, } /// Tooltip that is just text @@ -21,16 +22,19 @@ pub fn text<'a, M: 'a>(text: &'a str, style: Style) -> Element<'a, M, ui::IcedRe .size(style.text_size), ) .style(style.container) + .padding(style.padding) .into() } -pub trait WithTooltip<'a, M, R: iced::Renderer> { +pub trait WithTooltip<'a, M, R: ui::widget::tooltip::Renderer> { fn with_tooltip(self, manager: &'a TooltipManager, hover_content: H) -> Tooltip<'a, M, R> where H: 'a + FnMut() -> Element<'a, M, R>; } -impl<'a, M, R: iced::Renderer, E: Into>> WithTooltip<'a, M, R> for E { +impl<'a, M, R: ui::widget::tooltip::Renderer, E: Into>> WithTooltip<'a, M, R> + for E +{ fn with_tooltip(self, manager: &'a TooltipManager, hover_content: H) -> Tooltip<'a, M, R> where H: 'a + FnMut() -> Element<'a, M, R>, diff --git a/voxygen/src/ui/ice/renderer/mod.rs b/voxygen/src/ui/ice/renderer/mod.rs index b1f200aa45..c507b4c42f 100644 --- a/voxygen/src/ui/ice/renderer/mod.rs +++ b/voxygen/src/ui/ice/renderer/mod.rs @@ -181,7 +181,7 @@ impl IcedRenderer { //self.current_scissor = default_scissor(renderer); - self.draw_primitive(primitive, Vec2::zero(), renderer); + self.draw_primitive(primitive, Vec2::zero(), 1.0, renderer); // Enter the final command. self.draw_commands.push(match self.current_state { @@ -434,12 +434,18 @@ impl IcedRenderer { .collect() } - fn draw_primitive(&mut self, primitive: Primitive, offset: Vec2, renderer: &mut Renderer) { + fn draw_primitive( + &mut self, + primitive: Primitive, + offset: Vec2, + alpha: f32, + renderer: &mut Renderer, + ) { match primitive { Primitive::Group { primitives } => { primitives .into_iter() - .for_each(|p| self.draw_primitive(p, offset, renderer)); + .for_each(|p| self.draw_primitive(p, offset, alpha, renderer)); }, Primitive::Image { handle, @@ -447,8 +453,9 @@ impl IcedRenderer { color, } => { let color = srgba_to_linear(color.map(|e| e as f32 / 255.0)); + let color = apply_alpha(color, alpha); // Don't draw a transparent image. - if color[3] == 0.0 { + if color.a == 0.0 { return; } @@ -524,7 +531,9 @@ impl IcedRenderer { bottom_linear_color, } => { // Don't draw a transparent rectangle. - if top_linear_color[3] == 0.0 && bottom_linear_color[3] == 0.0 { + let top_linear_color = apply_alpha(top_linear_color, alpha); + let bottom_linear_color = apply_alpha(bottom_linear_color, alpha); + if top_linear_color.a == 0.0 && bottom_linear_color.a == 0.0 { return; } @@ -552,8 +561,9 @@ impl IcedRenderer { bounds, linear_color, } => { + let linear_color = apply_alpha(linear_color, alpha); // Don't draw a transparent rectangle. - if linear_color[3] == 0.0 { + if linear_color.a == 0.0 { return; } @@ -583,6 +593,7 @@ impl IcedRenderer { *horizontal_alignment, *vertical_alignment, */ } => { + let linear_color = apply_alpha(linear_color, alpha); self.switch_state(State::Plain); // TODO: makes sure we are not doing all this work for hidden text @@ -684,7 +695,7 @@ impl IcedRenderer { // TODO: cull primitives outside the current scissor // Renderer child - self.draw_primitive(*content, offset + clip_offset, renderer); + self.draw_primitive(*content, offset + clip_offset, alpha, renderer); // Reset scissor self.draw_commands.push(match self.current_state { @@ -698,6 +709,9 @@ impl IcedRenderer { self.draw_commands .push(DrawCommand::Scissor(self.window_scissor)); }, + Primitive::Opacity { alpha: a, content } => { + self.draw_primitive(*content, offset, alpha * a, renderer); + }, Primitive::Nothing => {}, } } @@ -806,4 +820,10 @@ impl iced::Renderer for IcedRenderer { } } +fn apply_alpha(color: Rgba, alpha: f32) -> Rgba { + Rgba { + a: alpha * color.a, + ..color + } +} // TODO: impl Debugger diff --git a/voxygen/src/ui/ice/renderer/primitive.rs b/voxygen/src/ui/ice/renderer/primitive.rs index 2ce358bc27..8230287130 100644 --- a/voxygen/src/ui/ice/renderer/primitive.rs +++ b/voxygen/src/ui/ice/renderer/primitive.rs @@ -36,5 +36,10 @@ pub enum Primitive { offset: vek::Vec2, content: Box, }, + // Make content translucent + Opacity { + alpha: f32, + content: Box, + }, Nothing, } diff --git a/voxygen/src/ui/ice/renderer/widget/mod.rs b/voxygen/src/ui/ice/renderer/widget/mod.rs index d165b6b2a8..8f9c84758e 100644 --- a/voxygen/src/ui/ice/renderer/widget/mod.rs +++ b/voxygen/src/ui/ice/renderer/widget/mod.rs @@ -13,3 +13,4 @@ mod slider; mod space; mod text; mod text_input; +mod tooltip; diff --git a/voxygen/src/ui/ice/renderer/widget/tooltip.rs b/voxygen/src/ui/ice/renderer/widget/tooltip.rs new file mode 100644 index 0000000000..626096dd0e --- /dev/null +++ b/voxygen/src/ui/ice/renderer/widget/tooltip.rs @@ -0,0 +1,23 @@ +use super::super::{super::widget::tooltip, IcedRenderer, Primitive}; +use iced::{Element, Layout, Point}; + +impl tooltip::Renderer for IcedRenderer { + fn draw( + &mut self, + alpha: f32, + defaults: &Self::Defaults, + cursor_position: Point, + content: &Element<'_, M, Self>, + content_layout: Layout<'_>, + ) -> Self::Output { + let (primitive, cursor_interaction) = + content.draw(self, defaults, content_layout, cursor_position); + ( + Primitive::Opacity { + alpha, + content: Box::new(primitive), + }, + cursor_interaction, + ) + } +} diff --git a/voxygen/src/ui/ice/widget/tooltip.rs b/voxygen/src/ui/ice/widget/tooltip.rs index e686b2b4a1..0a7d496b2c 100644 --- a/voxygen/src/ui/ice/widget/tooltip.rs +++ b/voxygen/src/ui/ice/widget/tooltip.rs @@ -119,16 +119,37 @@ impl TooltipManager { fn update(&self, update: Update) { *self.update.lock().unwrap() = Some(update); } - fn showing(&self, aabr: Aabr) -> bool { + /// Returns an options with the position of the cursor when the tooltip + /// started being show and the transparency if it is fading + fn showing(&self, aabr: Aabr) -> Option<(Point, f32)> { match self.state { - State::Idle | State::Start(_) => false, - State::Showing(show) | State::Fading(_, show, _) => show.aabr == aabr, + State::Idle | State::Start(_) => None, + State::Showing(show) => (show.aabr == aabr).then(|| { + ( + Point { + x: show.hover_pos.x as f32, + y: show.hover_pos.y as f32, + }, + 1.0, + ) + }), + State::Fading(start, show, _) => (show.aabr == aabr) + .then(|| { + ( + Point { + x: show.hover_pos.x as f32, + y: show.hover_pos.y as f32, + }, + 1.0 - start.elapsed().as_secs_f32() / self.fade_dur.as_secs_f32(), + ) + }) + .filter(|(_, fade)| *fade > 0.0), } } } /// A widget used to display tooltips when the content element is hovered -pub struct Tooltip<'a, M, R: iced::Renderer> { +pub struct Tooltip<'a, M, R: self::Renderer> { content: Element<'a, M, R>, hover_content: Box Element<'a, M, R>>, manager: &'a TooltipManager, @@ -136,7 +157,7 @@ pub struct Tooltip<'a, M, R: iced::Renderer> { impl<'a, M, R> Tooltip<'a, M, R> where - R: iced::Renderer, + R: self::Renderer, { pub fn new(content: C, hover_content: H, manager: &'a TooltipManager) -> Self where @@ -153,7 +174,7 @@ where impl<'a, M, R> Widget for Tooltip<'a, M, R> where - R: iced::Renderer, + R: self::Renderer, { fn width(&self) -> Length { self.content.width() } @@ -207,13 +228,10 @@ where let bounds = layout.bounds(); let aabr = aabr_from_bounds(bounds); - self.manager.showing(aabr).then(|| { + self.manager.showing(aabr).map(|(pos, alpha)| { iced::overlay::Element::new( - Point { - x: self.manager.hover_pos.x as f32, - y: self.manager.hover_pos.y as f32, - }, - Box::new(Overlay::new((self.hover_content)(), bounds)), + pos, + Box::new(Overlay::new((self.hover_content)(), bounds, alpha)), ) }) } @@ -227,7 +245,7 @@ where impl<'a, M, R> From> for Element<'a, M, R> where - R: 'a + iced::Renderer, + R: 'a + self::Renderer, M: 'a, { fn from(tooltip: Tooltip<'a, M, R>) -> Element<'a, M, R> { Element::new(tooltip) } @@ -239,41 +257,56 @@ fn aabr_from_bounds(bounds: iced::Rectangle) -> Aabr { Aabr { min, max } } -struct Overlay<'a, M, R: iced::Renderer> { +struct Overlay<'a, M, R: self::Renderer> { content: Element<'a, M, R>, /// Area to avoid overlapping with avoid: Rectangle, + /// Alpha for fading out + alpha: f32, } -impl<'a, M, R: iced::Renderer> Overlay<'a, M, R> { - pub fn new(content: Element<'a, M, R>, avoid: Rectangle) -> Self { Self { content, avoid } } +impl<'a, M, R: self::Renderer> Overlay<'a, M, R> { + pub fn new(content: Element<'a, M, R>, avoid: Rectangle, alpha: f32) -> Self { + Self { + content, + avoid, + alpha, + } + } } impl<'a, M, R> iced::Overlay for Overlay<'a, M, R> where - R: iced::Renderer, + R: self::Renderer, { fn layout(&self, renderer: &R, bounds: Size, position: Point) -> layout::Node { - // TODO: Avoid avoid area - let space_below = bounds.height - position.y; - let space_above = position.y; + let avoid = self.avoid; + const PAD: f32 = 8.0; // TODO: allow configuration + let space_above = (avoid.y - PAD).max(0.0); + let space_below = (bounds.height - avoid.y - avoid.height - PAD).max(0.0); + //let space_left = avoid.x.min(0.0) + //let space_right = (bounds.width - avoid.x - avoid.width).min(0.0); let limits = layout::Limits::new( Size::ZERO, - Size::new( - bounds.width - position.x, - if space_below > space_above { - space_below - } else { - space_above - }, - ), - ) - .width(self.content.width()); + Size::new(bounds.width, space_above.max(space_below)), + ); + //.width(self.content.width()); let mut node = self.content.layout(renderer, &limits); - node.move_to(position - iced::Vector::new(0.0, node.size().height)); + let size = node.size(); + + node.move_to(Point { + x: (bounds.width - size.width).min(position.x), + y: if space_above >= space_below { + avoid.y - size.height - PAD + } else { + avoid.y + avoid.height + PAD + }, + }); + + node } fn hash_layout(&self, state: &mut Hasher, position: Point) { @@ -292,7 +325,17 @@ where layout: Layout<'_>, cursor_position: Point, ) -> R::Output { - self.content - .draw(renderer, defaults, layout, cursor_position) + renderer.draw(self.alpha, defaults, cursor_position, &self.content, layout) } } + +pub trait Renderer: iced::Renderer { + fn draw( + &mut self, + alpha: f32, + defaults: &Self::Defaults, + cursor_position: Point, + content: &Element<'_, M, Self>, + content_layout: Layout<'_>, + ) -> Self::Output; +}