Fix tooltip border images, fade tooltips properly, improve tooltip positioning

This commit is contained in:
Imbris
2020-10-18 17:30:17 -04:00
parent d8854e9300
commit 3b664d2efb
9 changed files with 164 additions and 61 deletions

BIN
assets/voxygen/element/frames/tooltip/corner.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/frames/tooltip/edge.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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<u8> = Rgba::new(20, 18, 10, 255); pub const TOOLTIP_BACK_COLOR: Rgba<u8> = Rgba::new(20, 18, 10, 255);
const FILL_FRAC_ONE: f32 = 0.77; const FILL_FRAC_ONE: f32 = 0.77;
const FILL_FRAC_TWO: f32 = 0.60; const FILL_FRAC_TWO: f32 = 0.60;
const TOOLTIP_HOVER_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(500); 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_HAMMER: &str = "common.items.weapons.hammer.starter_hammer";
const STARTER_BOW: &str = "common.items.weapons.bow.starter_bow"; const STARTER_BOW: &str = "common.items.weapons.bow.starter_bow";
@ -112,8 +112,8 @@ image_ids_ice! {
button_press: "voxygen.element.buttons.button_press", button_press: "voxygen.element.buttons.button_press",
// Tooltips // Tooltips
tt_edge: "voxygen/element/frames/tt_test_edge", tt_edge: "voxygen/element/frames/tooltip/edge",
tt_corner: "voxygen/element/frames/tt_test_corner_tr", tt_corner: "voxygen/element/frames/tooltip/corner",
} }
} }
@ -304,7 +304,8 @@ impl Controls {
imgs.tt_edge, imgs.tt_edge,
), ),
text_color: TEXT_COLOR, 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) let version = iced::Text::new(&self.version)
@ -384,7 +385,7 @@ impl Controls {
.map(|(i, (character, (select_button, delete_button)))| { .map(|(i, (character, (select_button, delete_button)))| {
Overlay::new( Overlay::new(
Button::new( Button::new(
select_button, delete_button,
Space::new(Length::Units(16), Length::Units(16)), Space::new(Length::Units(16), Length::Units(16)),
) )
.style( .style(
@ -392,10 +393,19 @@ impl Controls {
.hover_image(imgs.delete_button_hover) .hover_image(imgs.delete_button_hover)
.press_image(imgs.delete_button_press), .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( AspectRatioContainer::new(
Button::new( Button::new(
delete_button, select_button,
Column::with_children(vec![ Column::with_children(vec![
Text::new(&character.character.alias).into(), Text::new(&character.character.alias).into(),
// TODO: only construct string once when characters are // TODO: only construct string once when characters are
@ -411,7 +421,7 @@ impl Controls {
.into(), .into(),
]), ]),
) )
.padding(8) .padding(10)
.style( .style(
style::button::Style::new(imgs.selection) style::button::Style::new(imgs.selection)
.hover_image(imgs.selection_hover) .hover_image(imgs.selection_hover)
@ -419,16 +429,7 @@ impl Controls {
) )
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.on_press(Message::Select(i)) .on_press(Message::Select(i)),
.with_tooltip(
tooltip_manager,
move || {
tooltip::text(
i18n.get("char_selection.delete_permanently"),
tooltip_style,
)
},
),
) )
.ratio_of_image(imgs.selection), .ratio_of_image(imgs.selection),
) )

View File

@ -11,6 +11,7 @@ pub struct Style {
pub container: style::container::Style, pub container: style::container::Style,
pub text_color: iced::Color, pub text_color: iced::Color,
pub text_size: u16, pub text_size: u16,
pub padding: u16,
} }
/// Tooltip that is just text /// 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), .size(style.text_size),
) )
.style(style.container) .style(style.container)
.padding(style.padding)
.into() .into()
} }
pub trait WithTooltip<'a, M, R: iced::Renderer> { pub trait WithTooltip<'a, M, R: ui::widget::tooltip::Renderer> {
fn with_tooltip<H>(self, manager: &'a TooltipManager, hover_content: H) -> Tooltip<'a, M, R> fn with_tooltip<H>(self, manager: &'a TooltipManager, hover_content: H) -> Tooltip<'a, M, R>
where where
H: 'a + FnMut() -> Element<'a, M, R>; H: 'a + FnMut() -> Element<'a, M, R>;
} }
impl<'a, M, R: iced::Renderer, E: Into<Element<'a, M, R>>> WithTooltip<'a, M, R> for E { impl<'a, M, R: ui::widget::tooltip::Renderer, E: Into<Element<'a, M, R>>> WithTooltip<'a, M, R>
for E
{
fn with_tooltip<H>(self, manager: &'a TooltipManager, hover_content: H) -> Tooltip<'a, M, R> fn with_tooltip<H>(self, manager: &'a TooltipManager, hover_content: H) -> Tooltip<'a, M, R>
where where
H: 'a + FnMut() -> Element<'a, M, R>, H: 'a + FnMut() -> Element<'a, M, R>,

View File

@ -181,7 +181,7 @@ impl IcedRenderer {
//self.current_scissor = default_scissor(renderer); //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. // Enter the final command.
self.draw_commands.push(match self.current_state { self.draw_commands.push(match self.current_state {
@ -434,12 +434,18 @@ impl IcedRenderer {
.collect() .collect()
} }
fn draw_primitive(&mut self, primitive: Primitive, offset: Vec2<u32>, renderer: &mut Renderer) { fn draw_primitive(
&mut self,
primitive: Primitive,
offset: Vec2<u32>,
alpha: f32,
renderer: &mut Renderer,
) {
match primitive { match primitive {
Primitive::Group { primitives } => { Primitive::Group { primitives } => {
primitives primitives
.into_iter() .into_iter()
.for_each(|p| self.draw_primitive(p, offset, renderer)); .for_each(|p| self.draw_primitive(p, offset, alpha, renderer));
}, },
Primitive::Image { Primitive::Image {
handle, handle,
@ -447,8 +453,9 @@ impl IcedRenderer {
color, color,
} => { } => {
let color = srgba_to_linear(color.map(|e| e as f32 / 255.0)); 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. // Don't draw a transparent image.
if color[3] == 0.0 { if color.a == 0.0 {
return; return;
} }
@ -524,7 +531,9 @@ impl IcedRenderer {
bottom_linear_color, bottom_linear_color,
} => { } => {
// Don't draw a transparent rectangle. // 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; return;
} }
@ -552,8 +561,9 @@ impl IcedRenderer {
bounds, bounds,
linear_color, linear_color,
} => { } => {
let linear_color = apply_alpha(linear_color, alpha);
// Don't draw a transparent rectangle. // Don't draw a transparent rectangle.
if linear_color[3] == 0.0 { if linear_color.a == 0.0 {
return; return;
} }
@ -583,6 +593,7 @@ impl IcedRenderer {
*horizontal_alignment, *horizontal_alignment,
*vertical_alignment, */ *vertical_alignment, */
} => { } => {
let linear_color = apply_alpha(linear_color, alpha);
self.switch_state(State::Plain); self.switch_state(State::Plain);
// TODO: makes sure we are not doing all this work for hidden text // 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 // TODO: cull primitives outside the current scissor
// Renderer child // Renderer child
self.draw_primitive(*content, offset + clip_offset, renderer); self.draw_primitive(*content, offset + clip_offset, alpha, renderer);
// Reset scissor // Reset scissor
self.draw_commands.push(match self.current_state { self.draw_commands.push(match self.current_state {
@ -698,6 +709,9 @@ impl IcedRenderer {
self.draw_commands self.draw_commands
.push(DrawCommand::Scissor(self.window_scissor)); .push(DrawCommand::Scissor(self.window_scissor));
}, },
Primitive::Opacity { alpha: a, content } => {
self.draw_primitive(*content, offset, alpha * a, renderer);
},
Primitive::Nothing => {}, Primitive::Nothing => {},
} }
} }
@ -806,4 +820,10 @@ impl iced::Renderer for IcedRenderer {
} }
} }
fn apply_alpha(color: Rgba<f32>, alpha: f32) -> Rgba<f32> {
Rgba {
a: alpha * color.a,
..color
}
}
// TODO: impl Debugger // TODO: impl Debugger

View File

@ -36,5 +36,10 @@ pub enum Primitive {
offset: vek::Vec2<u32>, offset: vek::Vec2<u32>,
content: Box<Primitive>, content: Box<Primitive>,
}, },
// Make content translucent
Opacity {
alpha: f32,
content: Box<Primitive>,
},
Nothing, Nothing,
} }

View File

@ -13,3 +13,4 @@ mod slider;
mod space; mod space;
mod text; mod text;
mod text_input; mod text_input;
mod tooltip;

View File

@ -0,0 +1,23 @@
use super::super::{super::widget::tooltip, IcedRenderer, Primitive};
use iced::{Element, Layout, Point};
impl tooltip::Renderer for IcedRenderer {
fn draw<M>(
&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,
)
}
}

View File

@ -119,16 +119,37 @@ impl TooltipManager {
fn update(&self, update: Update) { *self.update.lock().unwrap() = Some(update); } fn update(&self, update: Update) { *self.update.lock().unwrap() = Some(update); }
fn showing(&self, aabr: Aabr<i32>) -> 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<i32>) -> Option<(Point, f32)> {
match self.state { match self.state {
State::Idle | State::Start(_) => false, State::Idle | State::Start(_) => None,
State::Showing(show) | State::Fading(_, show, _) => show.aabr == aabr, 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 /// 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>, content: Element<'a, M, R>,
hover_content: Box<dyn 'a + FnMut() -> Element<'a, M, R>>, hover_content: Box<dyn 'a + FnMut() -> Element<'a, M, R>>,
manager: &'a TooltipManager, manager: &'a TooltipManager,
@ -136,7 +157,7 @@ pub struct Tooltip<'a, M, R: iced::Renderer> {
impl<'a, M, R> Tooltip<'a, M, R> impl<'a, M, R> Tooltip<'a, M, R>
where where
R: iced::Renderer, R: self::Renderer,
{ {
pub fn new<C, H>(content: C, hover_content: H, manager: &'a TooltipManager) -> Self pub fn new<C, H>(content: C, hover_content: H, manager: &'a TooltipManager) -> Self
where where
@ -153,7 +174,7 @@ where
impl<'a, M, R> Widget<M, R> for Tooltip<'a, M, R> impl<'a, M, R> Widget<M, R> for Tooltip<'a, M, R>
where where
R: iced::Renderer, R: self::Renderer,
{ {
fn width(&self) -> Length { self.content.width() } fn width(&self) -> Length { self.content.width() }
@ -207,13 +228,10 @@ where
let bounds = layout.bounds(); let bounds = layout.bounds();
let aabr = aabr_from_bounds(bounds); let aabr = aabr_from_bounds(bounds);
self.manager.showing(aabr).then(|| { self.manager.showing(aabr).map(|(pos, alpha)| {
iced::overlay::Element::new( iced::overlay::Element::new(
Point { pos,
x: self.manager.hover_pos.x as f32, Box::new(Overlay::new((self.hover_content)(), bounds, alpha)),
y: self.manager.hover_pos.y as f32,
},
Box::new(Overlay::new((self.hover_content)(), bounds)),
) )
}) })
} }
@ -227,7 +245,7 @@ where
impl<'a, M, R> From<Tooltip<'a, M, R>> for Element<'a, M, R> impl<'a, M, R> From<Tooltip<'a, M, R>> for Element<'a, M, R>
where where
R: 'a + iced::Renderer, R: 'a + self::Renderer,
M: 'a, M: 'a,
{ {
fn from(tooltip: Tooltip<'a, M, R>) -> Element<'a, M, R> { Element::new(tooltip) } 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<i32> {
Aabr { min, max } Aabr { min, max }
} }
struct Overlay<'a, M, R: iced::Renderer> { struct Overlay<'a, M, R: self::Renderer> {
content: Element<'a, M, R>, content: Element<'a, M, R>,
/// Area to avoid overlapping with /// Area to avoid overlapping with
avoid: Rectangle, avoid: Rectangle,
/// Alpha for fading out
alpha: f32,
} }
impl<'a, M, R: iced::Renderer> Overlay<'a, M, R> { impl<'a, M, R: self::Renderer> Overlay<'a, M, R> {
pub fn new(content: Element<'a, M, R>, avoid: Rectangle) -> Self { Self { content, avoid } } pub fn new(content: Element<'a, M, R>, avoid: Rectangle, alpha: f32) -> Self {
Self {
content,
avoid,
alpha,
}
}
} }
impl<'a, M, R> iced::Overlay<M, R> for Overlay<'a, M, R> impl<'a, M, R> iced::Overlay<M, R> for Overlay<'a, M, R>
where where
R: iced::Renderer, R: self::Renderer,
{ {
fn layout(&self, renderer: &R, bounds: Size, position: Point) -> layout::Node { fn layout(&self, renderer: &R, bounds: Size, position: Point) -> layout::Node {
// TODO: Avoid avoid area let avoid = self.avoid;
let space_below = bounds.height - position.y; const PAD: f32 = 8.0; // TODO: allow configuration
let space_above = position.y; 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( let limits = layout::Limits::new(
Size::ZERO, Size::ZERO,
Size::new( Size::new(bounds.width, space_above.max(space_below)),
bounds.width - position.x, );
if space_below > space_above { //.width(self.content.width());
space_below
} else {
space_above
},
),
)
.width(self.content.width());
let mut node = self.content.layout(renderer, &limits); 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) { fn hash_layout(&self, state: &mut Hasher, position: Point) {
@ -292,7 +325,17 @@ where
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
) -> R::Output { ) -> R::Output {
self.content renderer.draw(self.alpha, defaults, cursor_position, &self.content, layout)
.draw(renderer, defaults, layout, cursor_position)
} }
} }
pub trait Renderer: iced::Renderer {
fn draw<M>(
&mut self,
alpha: f32,
defaults: &Self::Defaults,
cursor_position: Point,
content: &Element<'_, M, Self>,
content_layout: Layout<'_>,
) -> Self::Output;
}