Various fixes/improvements

- On Linux, `fm-match` is used to query for the default fonts.
- DynamicComponents now have their own trait and can now be specified
  with a constant or dynamic.
- Roboto Flex is now always loaded when the feature is enabled.
  Overriding the default sans serif font prefers the overridden value,
  then roboto, then the result of fc-match/fontdb's default.
- Button now supports background colors being set on a transparent
  button.
This commit is contained in:
Jonathan Johnson 2023-12-03 15:35:37 -08:00
parent 55eea5fad3
commit 17847d6947
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
7 changed files with 169 additions and 77 deletions

View file

@ -1,14 +1,19 @@
use gooey::styles::components::FontFamily;
use gooey::styles::FontFamilyList;
use gooey::widget::MakeWidget;
use gooey::Run;
use kludgine::cosmic_text::FamilyOwned;
use kludgine::figures::units::Lp;
fn main() -> gooey::Result {
include_str!("./nested-scroll.rs")
.vertical_scroll()
.with(&FontFamily, FontFamilyList::from(FamilyOwned::Monospace))
.height(Lp::inches(3))
.and(
include_str!("./canvas.rs")
.vertical_scroll()
.with(&FontFamily, FontFamilyList::from(FamilyOwned::Monospace))
.height(Lp::inches(3)),
)
.into_rows()

View file

@ -1111,6 +1111,19 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
self.effective_styles.get(query, self)
}
/// Queries the widget hierarchy for a single style component.
///
/// This function traverses up the widget hierarchy looking for the
/// component being requested. If a matching component is found, it will be
/// returned.
#[must_use]
pub fn try_get<Component: ComponentDefinition>(
&self,
query: &Component,
) -> Option<Component::ComponentType> {
self.effective_styles.try_get(query, self)
}
pub(crate) fn handle(&self) -> WindowHandle {
WindowHandle {
kludgine: self.window.handle(),

View file

@ -66,9 +66,13 @@ impl Styles {
pub fn insert_dynamic(
&mut self,
name: &impl NamedComponent,
dynamic: impl Into<DynamicComponent>,
dynamic: impl IntoDynamicComponentValue,
) {
self.insert(name, Component::Dynamic(dynamic.into()));
let component = match dynamic.into_dynamic_component() {
Value::Constant(dynamic) => Value::Constant(Component::Dynamic(dynamic)),
Value::Dynamic(dynamic) => Value::Dynamic(dynamic.map_each_cloned(Component::Dynamic)),
};
self.insert(name, component);
}
/// Adds a [`Component`] for the name provided and returns self.
@ -91,7 +95,7 @@ impl Styles {
pub fn with_dynamic<C: ComponentDefinition>(
mut self,
name: &C,
dynamic: impl Into<DynamicComponent>,
dynamic: impl IntoDynamicComponentValue,
) -> Self {
self.insert_dynamic(name, dynamic);
self
@ -145,6 +149,23 @@ impl Styles {
}
}
/// Returns the component associated with the given name, if a value is
/// specified.
#[must_use]
pub fn try_get<Named>(
&self,
component: &Named,
context: &WidgetContext<'_, '_>,
) -> Option<Named::ComponentType>
where
Named: ComponentDefinition + ?Sized,
{
self.0
.components
.get(&component.name())
.and_then(|component| Self::resolve_component(component, context))
}
/// Returns the component associated with the given name, or if not found,
/// returns the default value provided by the definition.
#[must_use]
@ -156,10 +177,7 @@ impl Styles {
where
Named: ComponentDefinition + ?Sized,
{
self.0
.components
.get(&component.name())
.and_then(|component| Self::resolve_component(component, context))
self.try_get(component, context)
.unwrap_or_else(|| component.default_value(context))
}
@ -186,6 +204,29 @@ impl Debug for Styles {
}
}
impl FromIterator<(ComponentName, Component)> for Styles {
fn from_iter<T: IntoIterator<Item = (ComponentName, Component)>>(iter: T) -> Self {
let iter = iter.into_iter();
let mut styles = Self::with_capacity(iter.size_hint().0);
for (name, component) in iter {
styles.insert_named(name, component);
}
styles
}
}
impl IntoIterator for Styles {
type IntoIter = hash_map::IntoIter<ComponentName, Value<Component>>;
type Item = (ComponentName, Value<Component>);
fn into_iter(self) -> Self::IntoIter {
Arc::try_unwrap(self.0)
.unwrap_or_else(|err| err.as_ref().clone())
.components
.into_iter()
}
}
#[derive(Default, Clone)]
struct StyleData {
components: AHashMap<ComponentName, Value<Component>>,
@ -226,51 +267,35 @@ where
}
}
impl FromIterator<(ComponentName, Component)> for Styles {
fn from_iter<T: IntoIterator<Item = (ComponentName, Component)>>(iter: T) -> Self {
let iter = iter.into_iter();
let mut styles = Self::with_capacity(iter.size_hint().0);
for (name, component) in iter {
styles.insert_named(name, component);
}
styles
/// A type that can convert into a [`Value`] containing a [`DynamicComponent`].
pub trait IntoDynamicComponentValue {
/// Returns this type converted into a dynamic component value.
fn into_dynamic_component(self) -> Value<DynamicComponent>;
}
impl IntoDynamicComponentValue for DynamicComponent {
fn into_dynamic_component(self) -> Value<DynamicComponent> {
Value::Constant(self)
}
}
impl IntoIterator for Styles {
type IntoIter = hash_map::IntoIter<ComponentName, Value<Component>>;
type Item = (ComponentName, Value<Component>);
fn into_iter(self) -> Self::IntoIter {
Arc::try_unwrap(self.0)
.unwrap_or_else(|err| err.as_ref().clone())
.components
.into_iter()
impl<T> IntoDynamicComponentValue for T
where
T: ComponentDefinition + Clone + RefUnwindSafe + Send + Sync + 'static,
{
fn into_dynamic_component(self) -> Value<DynamicComponent> {
Value::Constant(DynamicComponent::from(self))
}
}
// /// An iterator over the owned contents of a [`Styles`] instance.
// pub struct StylesIntoIter {
// main: hash_map::IntoIter<ComponentName, Value<Component>>,
// }
// impl Iterator for StylesIntoIter {
// type Item = (ComponentName, Value<Component>);
// fn next(&mut self) -> Option<Self::Item> {
// loop {
// if let Some((group, names)) = &mut self.names {
// if let Some((name, component)) = names.next() {
// return Some((ComponentName::new(group.clone(), name), component));
// }
// self.names = None;
// }
// let (group, names) = self.main.next()?;
// self.names = Some((group, names.into_iter()));
// }
// }
// }
impl<T> IntoDynamicComponentValue for Dynamic<T>
where
T: ComponentDefinition + Clone + RefUnwindSafe + Send + Sync + 'static,
{
fn into_dynamic_component(self) -> Value<DynamicComponent> {
Value::Dynamic(self.map_each_into())
}
}
/// A value of a style component.
#[derive(Debug, Clone, PartialEq)]
@ -1019,21 +1044,6 @@ where
}
}
/// A type that represents a named component with a default value.
pub trait ComponentDefaultvalue: NamedComponent {
/// Returns the default value for this component.
fn default_component_value(&self, context: &WidgetContext<'_, '_>) -> Component;
}
impl<T> ComponentDefaultvalue for T
where
T: ComponentDefinition,
{
fn default_component_value(&self, context: &WidgetContext<'_, '_>) -> Component {
self.default_value(context).into_component()
}
}
impl NamedComponent for ComponentName {
fn name(&self) -> Cow<'_, ComponentName> {
Cow::Borrowed(self)

View file

@ -30,8 +30,8 @@ use crate::styles::components::{
TextSize7, TextSize8,
};
use crate::styles::{
ComponentDefinition, ContainerLevel, Dimension, DimensionRange, DynamicComponent, Edges,
IntoComponentValue, Styles, ThemePair, VisualOrder,
ComponentDefinition, ContainerLevel, Dimension, DimensionRange, Edges, IntoComponentValue,
IntoDynamicComponentValue, Styles, ThemePair, VisualOrder,
};
use crate::tree::Tree;
use crate::utils::IgnorePoison;
@ -712,7 +712,7 @@ pub trait MakeWidget: Sized {
fn with_dynamic<C: ComponentDefinition>(
self,
name: &C,
dynamic: impl Into<DynamicComponent>,
dynamic: impl IntoDynamicComponentValue,
) -> Style
where
Value<C::ComponentType>: IntoComponentValue,

View file

@ -32,6 +32,7 @@ pub struct Button {
pub on_click: Option<Callback<()>>,
/// The kind of button to draw.
pub kind: Value<ButtonKind>,
focusable: bool,
buttons_pressed: usize,
cached_state: CacheState,
active_colors: Option<Dynamic<ButtonColors>>,
@ -140,6 +141,7 @@ impl Button {
active_colors: None,
kind: Value::Constant(ButtonKind::default()),
color_animation: AnimationHandle::default(),
focusable: true,
}
}
@ -162,6 +164,13 @@ impl Button {
self
}
/// Prevents focus being given to this button.
#[must_use]
pub fn prevent_focus(mut self) -> Self {
self.focusable = false;
self
}
fn invoke_on_click(&mut self, context: &WidgetContext<'_, '_>) {
if context.enabled() {
if let Some(on_click) = self.on_click.as_mut() {
@ -190,7 +199,9 @@ impl Button {
) -> ButtonColors {
match visual_state {
VisualState::Normal => ButtonColors {
background: Color::CLEAR_BLACK,
background: context
.try_get(&ButtonBackground)
.unwrap_or(Color::CLEAR_BLACK),
foreground: context.get(&TextColor),
outline: context.get(&ButtonOutline),
},
@ -205,7 +216,9 @@ impl Button {
outline: context.get(&ButtonActiveOutline),
},
VisualState::Disabled => ButtonColors {
background: Color::CLEAR_BLACK,
background: context
.try_get(&ButtonDisabledBackground)
.unwrap_or(Color::CLEAR_BLACK),
foreground: context.theme().surface.on_color_variant,
outline: context.get(&ButtonDisabledOutline),
},
@ -391,7 +404,7 @@ impl Widget for Button {
}
fn accept_focus(&mut self, context: &mut EventContext<'_, '_>) -> bool {
context.enabled() && context.get(&AutoFocusableControls).is_all()
self.focusable && context.enabled() && context.get(&AutoFocusableControls).is_all()
}
fn mouse_down(
@ -437,7 +450,7 @@ impl Widget for Button {
if self.buttons_pressed == 0 {
context.deactivate();
if let Some(location) = location {
if let (true, Some(location)) = (self.focusable, location) {
if Rect::from(context.last_layout().expect("must have been rendered").size)
.contains(location)
{

View file

@ -8,7 +8,7 @@ use crate::styles::components::{
LineHeight8, TextSize, TextSize1, TextSize2, TextSize3, TextSize4, TextSize5, TextSize6,
TextSize7, TextSize8,
};
use crate::styles::{ComponentDefinition, DynamicComponent, IntoComponentValue, Styles};
use crate::styles::{ComponentDefinition, IntoComponentValue, IntoDynamicComponentValue, Styles};
use crate::value::{IntoValue, Value};
use crate::widget::{MakeWidget, WidgetRef, WrapperWidget};
@ -58,7 +58,7 @@ impl Style {
pub fn with_dynamic<C: ComponentDefinition>(
mut self,
name: &C,
dynamic: impl Into<DynamicComponent>,
dynamic: impl IntoDynamicComponentValue,
) -> Style
where
Value<C::ComponentType>: IntoComponentValue,

View file

@ -5,6 +5,7 @@ use std::ffi::OsStr;
use std::ops::{Deref, DerefMut, Not};
use std::panic::{AssertUnwindSafe, UnwindSafe};
use std::path::Path;
use std::process::Command;
use std::string::ToString;
use std::sync::{MutexGuard, OnceLock};
@ -18,7 +19,7 @@ use kludgine::app::winit::event::{
use kludgine::app::winit::keyboard::{Key, NamedKey};
use kludgine::app::winit::window;
use kludgine::app::WindowBehavior as _;
use kludgine::cosmic_text::FamilyOwned;
use kludgine::cosmic_text::{Family, FamilyOwned};
use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Ranged, Rect, ScreenScale, Size};
use kludgine::render::Drawing;
@ -534,34 +535,53 @@ where
let theme = settings.theme.take().expect("theme always present");
let fontdb = graphics.font_system().db_mut();
if let Some(FamilyOwned::Name(name)) =
Graphics::inner_find_available_font_family(fontdb, &settings.serif_font_family)
.or_else(|| default_family(Family::Serif))
{
fontdb.set_serif_family(name);
}
let bundled_font_name;
#[cfg(feature = "roboto-flex")]
{
fontdb.load_font_data(include_bytes!("../assets/RobotoFlex.ttf").to_vec());
bundled_font_name = Some(String::from("Roboto Flex"));
}
#[cfg(not(feature = "roboto-flex"))]
{
bundled_font_name = None;
}
if let Some(FamilyOwned::Name(name)) =
Graphics::inner_find_available_font_family(fontdb, &settings.sans_serif_font_family)
.or_else(|| {
if let Some(name) = bundled_font_name {
Some(FamilyOwned::Name(name))
} else {
default_family(Family::SansSerif)
}
})
{
fontdb.set_sans_serif_family(name);
} else {
#[cfg(feature = "roboto-flex")]
{
fontdb.load_font_data(include_bytes!("../assets/RobotoFlex.ttf").to_vec());
fontdb.set_sans_serif_family("Roboto Flex");
}
}
if let Some(FamilyOwned::Name(name)) =
Graphics::inner_find_available_font_family(fontdb, &settings.fantasy_font_family)
.or_else(|| default_family(Family::Fantasy))
{
fontdb.set_fantasy_family(name);
}
if let Some(FamilyOwned::Name(name)) =
Graphics::inner_find_available_font_family(fontdb, &settings.monospace_font_family)
.or_else(|| default_family(Family::Monospace))
{
fontdb.set_monospace_family(name);
}
if let Some(FamilyOwned::Name(name)) =
Graphics::inner_find_available_font_family(fontdb, &settings.cursive_font_family)
.or_else(|| default_family(Family::Cursive))
{
fontdb.set_cursive_family(name);
}
@ -1292,3 +1312,34 @@ impl Ranged for ThemeMode {
const MAX: Self = Self::Dark;
const MIN: Self = Self::Light;
}
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "windows"))]
fn default_family(query: Family<'_>) -> Option<FamilyOwned> {
// fontdb uses system APIs to determine these defaults.
None
}
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "windows")))]
fn default_family(query: Family<'_>) -> Option<FamilyOwned> {
// fontdb does not yet support configuring itself automatically. We will try
// to use `fc-match` to query font config. Once this is supported, we can
// remove this functionality.
// <https://github.com/RazrFalcon/fontdb/issues/24>
let query = match query {
Family::Serif => "serif",
Family::SansSerif => "sans",
Family::Cursive => "cursive",
Family::Fantasy => "fantasy",
Family::Monospace => "monospace",
Family::Name(_) => return None,
};
Command::new("fc-match")
.arg("-f")
.arg("%{family}")
.arg(query)
.output()
.ok()
.and_then(|output| String::from_utf8(output.stdout).ok())
.map(FamilyOwned::Name)
}