From 353db9dc395a080e4797dd7739c78f4a4bb35211 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Wed, 13 Dec 2023 14:02:39 -0800 Subject: [PATCH] Added Opacity component Closes #87 --- Cargo.lock | 2 +- examples/tilemap.rs | 1 + src/animation.rs | 37 ++++++++++++++++++++++++++++++++++++- src/context.rs | 17 +++++++++++++++-- src/graphics.rs | 30 +++++++++++++++++++++++++++++- src/styles/components.rs | 5 ++++- src/widgets/layers.rs | 26 +++++++++++++++++++++----- src/window.rs | 2 +- 8 files changed, 108 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1121d68..9acb26e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1062,7 +1062,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kludgine" version = "0.1.0" -source = "git+https://github.com/khonsulabs/kludgine#90d2035c22bd92ddc5e7edf6c933886c55749bd3" +source = "git+https://github.com/khonsulabs/kludgine#2fffa618dda72fae415b2d25faf9dfe461662246" dependencies = [ "ahash", "alot", diff --git a/examples/tilemap.rs b/examples/tilemap.rs index c839ad6..83c080c 100644 --- a/examples/tilemap.rs +++ b/examples/tilemap.rs @@ -132,6 +132,7 @@ impl Object for Player { context.draw_texture( frame, Rect::new(center - zoomed_size / 2, Size::squared(zoomed_size)), + 1., ); } diff --git a/src/animation.rs b/src/animation.rs index f527337..5352f92 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -41,7 +41,7 @@ pub mod easings; use std::cmp::Ordering; use std::fmt::{Debug, Display}; -use std::ops::{ControlFlow, Deref, Div, Mul, Sub}; +use std::ops::{ControlFlow, Deref, Div, DivAssign, Mul, MulAssign, Sub}; use std::panic::{RefUnwindSafe, UnwindSafe}; use std::str::FromStr; use std::sync::{Arc, Mutex, MutexGuard, OnceLock}; @@ -1250,6 +1250,12 @@ impl Mul for ZeroToOne { } } +impl MulAssign for ZeroToOne { + fn mul_assign(&mut self, rhs: Self) { + self.0 *= rhs.0; + } +} + impl Div for ZeroToOne { type Output = Self; @@ -1258,6 +1264,12 @@ impl Div for ZeroToOne { } } +impl DivAssign for ZeroToOne { + fn div_assign(&mut self, rhs: Self) { + self.0 /= rhs.0; + } +} + impl Div for ZeroToOne { type Output = Self; @@ -1271,6 +1283,29 @@ impl Ranged for ZeroToOne { const MIN: Self = Self::ZERO; } +impl RequireInvalidation for ZeroToOne { + fn requires_invalidation(&self) -> bool { + false + } +} + +impl TryFrom for ZeroToOne { + type Error = Component; + + fn try_from(value: Component) -> Result { + match value { + Component::Percent(value) => Ok(value), + other => Err(other), + } + } +} + +impl From for Component { + fn from(value: ZeroToOne) -> Self { + Component::Percent(value) + } +} + /// An easing function for customizing animations. #[derive(Debug, Clone)] pub enum EasingFunction { diff --git a/src/context.rs b/src/context.rs index fff5ef8..f233e79 100644 --- a/src/context.rs +++ b/src/context.rs @@ -14,11 +14,12 @@ use kludgine::figures::{IntoSigned, Point, Px2D, Rect, Round, ScreenScale, Size, use kludgine::shapes::{Shape, StrokeOptions}; use kludgine::{Color, Kludgine}; +use crate::animation::ZeroToOne; use crate::context::sealed::WindowHandle; use crate::graphics::Graphics; use crate::styles::components::{ CornerRadius, FontFamily, FontStyle, FontWeight, HighlightColor, LayoutOrder, LineHeight, - TextSize, WidgetBackground, + Opacity, TextSize, WidgetBackground, }; use crate::styles::{ComponentDefinition, Styles, Theme, ThemePair}; use crate::utils::IgnorePoison; @@ -555,19 +556,31 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, ' Widget: ManageWidget, Widget::Managed: MapManagedWidget>, { + let opacity = self.get(&Opacity); widget.manage(self).map(|widget| { let widget = self.widget.for_other(&widget); let layout = widget.last_layout().map_or_else( || Rect::from(self.gfx.clip_rect().size).into_signed(), |rect| rect - self.gfx.region().origin, ); + let mut gfx = self.gfx.clipped_to(layout); + gfx.opacity *= opacity; GraphicsContext { widget, - gfx: Exclusive::Owned(self.gfx.clipped_to(layout)), + gfx: Exclusive::Owned(gfx), } }) } + /// Updates `self` to have `opacity`. + /// + /// This setting will be mixed with the current opacity value. + #[must_use] + pub fn with_opacity(mut self, opacity: impl Into) -> Self { + self.gfx.opacity *= opacity.into(); + self + } + /// Returns a new graphics context that renders to the `clip` rectangle. pub fn clipped_to(&mut self, clip: Rect) -> GraphicsContext<'_, 'window, '_, 'gfx, 'pass> { GraphicsContext { diff --git a/src/graphics.rs b/src/graphics.rs index 86e00d3..f3cd6d3 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -13,6 +13,7 @@ use kludgine::{ cosmic_text, ClipGuard, Color, Drawable, Kludgine, ShaderScalable, ShapeSource, TextureSource, }; +use crate::animation::ZeroToOne; use crate::styles::FontFamilyList; /// A 2d graphics context @@ -20,6 +21,7 @@ pub struct Graphics<'clip, 'gfx, 'pass> { renderer: RenderContext<'clip, 'gfx, 'pass>, region: Rect, font_state: &'clip mut FontState, + pub(crate) opacity: ZeroToOne, } enum RenderContext<'clip, 'gfx, 'pass> { @@ -35,6 +37,7 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> { region: renderer.clip_rect().into_signed(), renderer: RenderContext::Renderer(renderer), font_state, + opacity: ZeroToOne::ONE, } } @@ -112,6 +115,7 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> { renderer: RenderContext::Clipped(self.renderer.clipped_to(new_clip)), region, font_state: &mut *self.font_state, + opacity: self.opacity, } } @@ -173,6 +177,11 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> { Unit: Zero + ShaderScalable + figures::ScreenUnit + Copy, { let mut shape = shape.into(); + shape.opacity = Some( + shape + .opacity + .map_or(*self.opacity, |opacity| opacity * *self.opacity), + ); shape.translation += Point::::from_px(self.translation(), self.scale()); self.renderer.draw_shape(shape); } @@ -184,7 +193,8 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> { i32: From<::Signed>, { let translate = Point::::from_px(self.translation(), self.scale()); - self.renderer.draw_texture(texture, destination + translate); + self.renderer + .draw_texture(texture, destination + translate, *self.opacity); } /// Draws a shape that was created with texture coordinates, applying the @@ -199,6 +209,11 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> { Shape: ShapeSource + 'shape, { let mut shape = shape.into(); + shape.opacity = Some( + shape + .opacity + .map_or(*self.opacity, |opacity| opacity * *self.opacity), + ); shape.translation += Point::::from_px(self.translation(), self.scale()); self.renderer.draw_textured_shape(shape, texture); } @@ -219,6 +234,10 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> { Unit: ScreenUnit, { let mut text = text.into(); + text.opacity = Some( + text.opacity + .map_or(*self.opacity, |opacity| opacity * *self.opacity), + ); text.translation += Point::::from_px(self.translation(), self.scale()); self.renderer.draw_text(text); } @@ -239,6 +258,11 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> { Unit: ScreenUnit, { let mut buffer = buffer.into(); + buffer.opacity = Some( + buffer + .opacity + .map_or(*self.opacity, |opacity| opacity * *self.opacity), + ); buffer.translation += Point::::from_px(self.translation(), self.scale()); self.renderer .draw_text_buffer(buffer, default_color, origin); @@ -272,6 +296,10 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> { Unit: ScreenUnit, { let mut text = text.into(); + text.opacity = Some( + text.opacity + .map_or(*self.opacity, |opacity| opacity * *self.opacity), + ); text.translation += Point::::from_px(self.translation(), self.scale()); self.renderer.draw_measured_text(text, origin); } diff --git a/src/styles/components.rs b/src/styles/components.rs index aa028bf..8decc34 100644 --- a/src/styles/components.rs +++ b/src/styles/components.rs @@ -6,7 +6,7 @@ use kludgine::shapes::CornerRadii; use kludgine::Color; use crate::animation::easings::{EaseInOutQuadradic, EaseInQuadradic, EaseOutQuadradic}; -use crate::animation::EasingFunction; +use crate::animation::{EasingFunction, ZeroToOne}; use crate::styles::{Dimension, FocusableWidgets, FontFamilyList, VisualOrder}; /// Defines a set of style components for Gooey. @@ -235,5 +235,8 @@ define_components! { Heading5FontFamily(FontFamilyList, "heading_font_family_5", @HeadingFontFamily) /// The [`FontFamilyList`] to apply to h6 headings. Heading6FontFamily(FontFamilyList, "heading_font_family_6", @HeadingFontFamily) + + /// The opaqueness of drawing calls + Opacity(ZeroToOne, "opacity", ZeroToOne::ONE) } } diff --git a/src/widgets/layers.rs b/src/widgets/layers.rs index e407693..59953c4 100644 --- a/src/widgets/layers.rs +++ b/src/widgets/layers.rs @@ -1,6 +1,7 @@ //! Widgets that stack in the Z-direction. use std::fmt; +use std::time::Duration; use alot::{LotId, OrderedLots}; use gooey::widget::{RootBehavior, WidgetInstance}; @@ -8,6 +9,8 @@ use intentional::Assert; use kludgine::figures::units::{Px, UPx}; use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size, Zero}; +use crate::animation::easings::{EaseInQuadradic, EaseOutQuadradic}; +use crate::animation::{AnimationTarget, Spawn, ZeroToOne}; use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext}; use crate::value::{Dynamic, DynamicGuard, Generation, IntoValue, Value}; use crate::widget::{ @@ -47,7 +50,7 @@ impl Layers { if self .mounted .get(index) - .map_or(true, |child| child != widget) + .map_or(true, |child| &child.widget != widget) { // These entries do not match. See if we can find the // new id somewhere else, if so we can swap the entries. @@ -56,7 +59,7 @@ impl Layers { .iter() .enumerate() .skip(index + 1) - .find(|(_, child)| *child == widget) + .find(|(_, child)| &child.widget == widget) { self.mounted.swap(index, swap_index); } else { @@ -81,8 +84,8 @@ impl Widget for Layers { fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { self.synchronize_children(&mut context.as_event_context()); - for child in &self.mounted { - context.for_other(child).redraw(); + for mounted in &self.mounted { + context.for_other(mounted).redraw(); } } @@ -186,6 +189,7 @@ impl OverlayLayer { requires_hover: false, on_dismiss: None, layout: None, + opacity: Dynamic::default(), }, } } @@ -200,7 +204,8 @@ impl Widget for OverlayLayer { continue; }; - context.for_other(mounted).redraw(); + let opacity = child.opacity.get_tracking_refresh(context); + context.for_other(mounted).with_opacity(opacity).redraw(); } } @@ -611,6 +616,7 @@ impl OverlayBuilder<'_> { /// Shows this overlay, returning a handle that to the displayed overlay. #[must_use] pub fn show(self) -> OverlayHandle { + self.fade_in(); self.overlay.state.map_mut(|state| { state.new_overlays += 1; OverlayHandle { @@ -620,11 +626,21 @@ impl OverlayBuilder<'_> { } }) } + + fn fade_in(&self) { + self.layout + .opacity + .transition_to(ZeroToOne::ONE) + .over(Duration::from_millis(250)) + .with_easing(EaseOutQuadradic) + .launch(); + } } #[derive(Debug, Eq, PartialEq)] struct OverlayLayout { widget: WidgetRef, + opacity: Dynamic, relative_to: Option, direction: Direction, requires_hover: bool, diff --git a/src/window.rs b/src/window.rs index 150408a..046de08 100644 --- a/src/window.rs +++ b/src/window.rs @@ -769,7 +769,7 @@ where _window: kludgine::app::Window<'_, WindowCommand>, graphics: &mut kludgine::RenderingGraphics<'_, 'pass>, ) -> bool { - self.contents.render(graphics); + self.contents.render(1., graphics); !self.should_close }