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);
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),
)

View File

@ -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<H>(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<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>
where
H: 'a + FnMut() -> Element<'a, M, R>,

View File

@ -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<u32>, renderer: &mut Renderer) {
fn draw_primitive(
&mut self,
primitive: Primitive,
offset: Vec2<u32>,
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<f32>, alpha: f32) -> Rgba<f32> {
Rgba {
a: alpha * color.a,
..color
}
}
// TODO: impl Debugger

View File

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

View File

@ -13,3 +13,4 @@ mod slider;
mod space;
mod text;
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 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 {
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<dyn 'a + FnMut() -> 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<C, H>(content: C, hover_content: H, manager: &'a TooltipManager) -> Self
where
@ -153,7 +174,7 @@ where
impl<'a, M, R> Widget<M, R> 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<Tooltip<'a, M, R>> 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<i32> {
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<M, R> 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<M>(
&mut self,
alpha: f32,
defaults: &Self::Defaults,
cursor_position: Point,
content: &Element<'_, M, Self>,
content_layout: Layout<'_>,
) -> Self::Output;
}