Skip to main content

styled_text/
styled_text.rs

1// Copyright 2020 The Druid Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Example of dynamic text styling
16
17// On Windows platform, don't show a console when opening the app.
18#![windows_subsystem = "windows"]
19
20#[allow(deprecated)]
21use druid::widget::Parse;
22use druid::widget::{
23    Checkbox, CrossAxisAlignment, Flex, Label, LensWrap, MainAxisAlignment, Painter, Scroll,
24    Stepper, TextBox,
25};
26use druid::{
27    theme, AppLauncher, Color, Data, FontDescriptor, FontFamily, Key, Lens, LensExt,
28    LocalizedString, PlatformError, RenderContext, Widget, WidgetExt, WindowDesc,
29};
30use std::fmt::Display;
31
32// This is a custom key we'll use with Env to set and get our font.
33const MY_CUSTOM_FONT: Key<FontDescriptor> = Key::new("org.linebender.example.my-custom-font");
34
35const COLUMN_WIDTH: f64 = 360.0;
36
37#[derive(Clone, Lens, Data)]
38struct AppData {
39    text: String,
40    size: f64,
41    mono: bool,
42}
43impl Display for AppData {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        write!(
46            f,
47            "Size {:.1}{}: {}",
48            self.size,
49            if self.mono { " mono" } else { "" },
50            self.text
51        )
52    }
53}
54
55pub fn main() -> Result<(), PlatformError> {
56    let main_window = WindowDesc::new(ui_builder()).title(
57        LocalizedString::new("styled-text-demo-window-title").with_placeholder("Type Styler"),
58    );
59    let data = AppData {
60        text: "Here's some sample text".to_string(),
61        size: 24.0,
62        mono: false,
63    };
64
65    AppLauncher::with_window(main_window)
66        .log_to_console()
67        .launch(data)?;
68
69    Ok(())
70}
71
72fn ui_builder() -> impl Widget<AppData> {
73    let my_painter = Painter::new(|ctx, _, _| {
74        let bounds = ctx.size().to_rect();
75        if ctx.is_hot() {
76            ctx.fill(bounds, &Color::rgba8(0, 0, 0, 128));
77        }
78
79        if ctx.is_active() {
80            ctx.stroke(bounds, &Color::WHITE, 2.0);
81        }
82    });
83
84    // This is Druid's default text style.
85    // It's set by theme::LABEL_COLOR and theme::UI_FONT
86    let label =
87        Label::new(|data: &String, _env: &_| format!("Default: {data}")).lens(AppData::text);
88
89    // The text_color, text_size, and font builder methods can override the
90    // defaults provided by the theme by passing in a Key or a concrete value.
91    //
92    // In this example, text_color receives a Key from the theme, text_size
93    // gets a custom key which we set with the env_scope wrapper, and the
94    // default font key (theme::FONT_NAME) is overridden in the env_scope
95    // wrapper. (Like text_color and text_size, the font can be set using the
96    // with_font builder method, but overriding here makes it easy to fall back
97    // to the default font)
98    let styled_label = Label::new(|data: &AppData, _env: &_| format!("{data}"))
99        .with_text_color(theme::PRIMARY_LIGHT)
100        .with_font(MY_CUSTOM_FONT)
101        .background(my_painter)
102        .on_click(|_, data, _| {
103            data.size *= 1.1;
104        })
105        .env_scope(|env: &mut druid::Env, data: &AppData| {
106            let new_font = if data.mono {
107                FontDescriptor::new(FontFamily::MONOSPACE)
108            } else {
109                FontDescriptor::new(FontFamily::SYSTEM_UI)
110            }
111            .with_size(data.size);
112            env.set(MY_CUSTOM_FONT, new_font);
113        });
114
115    let labels = Scroll::new(
116        Flex::column()
117            .cross_axis_alignment(CrossAxisAlignment::Start)
118            .with_child(label)
119            .with_default_spacer()
120            .with_child(styled_label),
121    )
122    .expand_height()
123    .fix_width(COLUMN_WIDTH);
124
125    let stepper = Stepper::new()
126        .with_range(0.0, 100.0)
127        .with_step(1.0)
128        .with_wraparound(false)
129        .lens(AppData::size);
130
131    // TODO: Replace Parse usage with TextBox::with_formatter
132    #[allow(deprecated)]
133    let stepper_textbox = LensWrap::new(
134        Parse::new(TextBox::new()),
135        AppData::size.map(|x| Some(*x), |x, y| *x = y.unwrap_or(24.0)),
136    );
137
138    let mono_checkbox = Checkbox::new("Monospace").lens(AppData::mono);
139    let stepper_row = Flex::row()
140        .with_child(stepper_textbox)
141        .with_child(stepper)
142        .with_default_spacer()
143        .with_child(mono_checkbox);
144
145    let input = TextBox::multiline()
146        .with_placeholder("Your sample text here :)")
147        .fix_width(COLUMN_WIDTH)
148        .fix_height(140.0)
149        .lens(AppData::text);
150
151    Flex::column()
152        .main_axis_alignment(MainAxisAlignment::Center)
153        .with_default_spacer()
154        .with_flex_child(labels, 1.0)
155        .with_default_spacer()
156        .with_child(input)
157        .with_default_spacer()
158        .with_child(stepper_row)
159        .with_default_spacer()
160}