mirror of
https://github.com/danbulant/cushy
synced 2026-06-14 20:11:04 +00:00
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:
parent
55eea5fad3
commit
17847d6947
7 changed files with 169 additions and 77 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
132
src/styles.rs
132
src/styles.rs
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue