Skip to content
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .vscode/settings.json
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete that file

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"rust-analyzer.linkedProjects": [
".\\examples\\rotation\\Cargo.toml"
],
"rust-analyzer.showUnlinkedFileNotification": false
}
8 changes: 8 additions & 0 deletions examples/rotation/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "rotation"
version = "0.1.0"
edition = "2021"

[dependencies]
im.workspace = true
floem = { path = "../.." }
163 changes: 163 additions & 0 deletions examples/rotation/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use floem::{
event::{Event, EventListener},
keyboard::{Key, NamedKey},
peniko::Color,
reactive::create_signal,
style::{Background, BorderColor, Outline, OutlineColor, Style, TextColor, Transition},
style_class,
view::View,
views::{label, stack, text, Decorators},
};

style_class!(pub Button);
style_class!(pub Label);
style_class!(pub Frame);

fn app_view() -> impl View {
let blue_button = Style::new()
.background(Color::rgb8(137, 145, 160))
.color(Color::WHITE)
.border(1.0)
.border_color(Color::rgb8(109, 121, 135))
.hover(|s| s.background(Color::rgb8(170, 175, 187)))
.transition(TextColor, Transition::linear(0.06))
.transition(BorderColor, Transition::linear(0.06))
.transition(Background, Transition::linear(0.06))
.transition(Outline, Transition::linear(0.1))
.focus_visible(|s| {
s.outline(2.0)
.outline_color(Color::WHITE.with_alpha_factor(0.7))
})
.disabled(|s| {
s.background(Color::DARK_GRAY.with_alpha_factor(0.1))
.border_color(Color::BLACK.with_alpha_factor(0.2))
})
.active(|s| s.background(Color::BLACK.with_alpha_factor(0.4)))
.padding(5.0)
.margin(3.0)
.border_radius(5.0);
let blue_theme = Style::new()
.background(Color::rgb8(95, 102, 118))
.transition(Background, Transition::linear(0.1))
.transition(TextColor, Transition::linear(0.1))
.color(Color::WHITE)
.class(Button, move |_| blue_button)
.class(Label, |s| {
s.margin(4.0).transition(TextColor, Transition::linear(0.1))
})
.font_size(12.0);

let green_button = Style::new()
.background(Color::rgb8(180, 188, 175))
.disabled(|s| {
s.background(Color::rgb8(180, 188, 175).with_alpha_factor(0.3))
.border_color(Color::rgb8(131, 145, 123).with_alpha_factor(0.3))
.color(Color::GRAY)
})
.active(|s| s.background(Color::rgb8(95, 105, 88)).color(Color::WHITE))
.color(Color::BLACK.with_alpha_factor(0.7))
.border(2.0)
.transition(TextColor, Transition::linear(0.3))
.transition(BorderColor, Transition::linear(0.3))
.transition(Background, Transition::linear(0.3))
.transition(Outline, Transition::linear(0.2))
.transition(OutlineColor, Transition::linear(0.2))
.outline_color(Color::rgba8(131, 145, 123, 0))
.focus_visible(|s| {
s.outline(10.0)
.outline_color(Color::rgb8(131, 145, 123).with_alpha_factor(0.3))
})
.border_color(Color::rgb8(131, 145, 123))
.hover(|s| s.background(Color::rgb8(204, 209, 201)))
.padding(8.0)
.border_radius(8.0)
.margin(6.0);
let green_theme = Style::new()
.background(Color::rgb8(227, 231, 226))
.transition(Background, Transition::linear(0.5))
.class(Button, move |_| green_button)
.class(Label, |s| {
s.margin(4.0).transition(TextColor, Transition::linear(0.5))
})
.class(Frame, |s| {
s.border(2.0)
.border_color(Color::rgb8(131, 145, 123).with_alpha_factor(0.2))
.border_radius(8.0)
.background(Color::WHITE.with_alpha_factor(0.1))
.padding(12.0)
})
.color(Color::BLACK.with_alpha_factor(0.5))
.font_size(16.0);

let (counter, set_counter) = create_signal(0);
let (theme, set_theme) = create_signal(false);
let view = stack((stack((
text("Toggle Theme")
.class(Button)
.on_click_stop({
move |_| {
set_theme.update(|theme| *theme = !*theme);
}
})
.style(|s| s.rotation_90())
.keyboard_navigatable(),
stack((
label(move || format!("Value: {}", counter.get())).class(Label),
text("Increment")
.class(Button)
.on_click_stop({
move |_| {
set_counter.update(|value| *value += 1);
}
})
.style(|s| s.rotation_90())
.keyboard_navigatable(),
text("Decrement")
.class(Button)
.on_click_stop({
move |_| {
set_counter.update(|value| *value -= 1);
}
})
.style(|s| s.rotation_180())
.keyboard_navigatable(),
text("Reset to 0")
.class(Button)
.on_click_stop(move |_| {
println!("Reset counter pressed"); // will not fire if button is disabled
set_counter.update(|value| *value = 0);
})
.disabled(move || counter.get() == 0)
.style(|s| s.rotation_270())
.keyboard_navigatable(),
))
.class(Frame)
.style(|s| s.items_center()),
))
.style(|s| s.items_center()),))
.style(move |_| {
if theme.get() {
blue_theme.clone()
} else {
green_theme.clone()
}
.width_full()
.height_full()
.flex_col()
.items_center()
.justify_center()
})
.window_title(|| "Themes Example".to_string());
let id = view.id();
view.on_event_stop(EventListener::KeyUp, move |e| {
if let Event::KeyUp(e) = e {
if e.key.logical_key == Key::Named(NamedKey::F11) {
id.inspect();
}
}
})
}

fn main() {
floem::launch(app_view);
}
33 changes: 33 additions & 0 deletions src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ impl StylePropValue for cosmic_text::Style {}
impl StylePropValue for TextOverflow {}
impl StylePropValue for LineHeightValue {}
impl StylePropValue for Size<LengthPercentage> {}
impl StylePropValue for Rotation {}

impl<T: StylePropValue> StylePropValue for Option<T> {
fn debug_view(&self) -> Option<AnyView> {
Expand Down Expand Up @@ -1020,6 +1021,14 @@ pub enum TextOverflow {
Ellipsis,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Rotation {
Rotation0,
Rotation90,
Rotation180,
Rotation270,
}

Comment on lines +1024 to +1031
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have better naming like:

Suggested change
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Rotation {
Rotation0,
Rotation90,
Rotation180,
Rotation270,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Rotation {
NoRotation,
RotateRight,
RotateUpsideDown,
RotateLeft,
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

although I don't feel like rotate is anyhow good

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preferably it should somehow comply with how CSS defines this attributes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, thanks for your advice. I will revise it right away

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CursorStyle {
Default,
Expand Down Expand Up @@ -1117,6 +1126,16 @@ impl<T> From<T> for StyleValue<T> {
Self::Val(x)
}
}
impl Rotation {
pub fn angle(self) -> f64 {
match self {
Rotation::Rotation0 => 0.0,
Rotation::Rotation90 => 90.0,
Rotation::Rotation180 => 180.0,
Rotation::Rotation270 => 270.0,
}
}
}

macro_rules! define_builtin_props {
(
Expand Down Expand Up @@ -1213,6 +1232,7 @@ define_builtin_props!(
LineHeight line_height nocb: Option<LineHeightValue> { inherited } = None,
AspectRatio aspect_ratio: Option<f32> {} = None,
Gap gap nocb: Size<LengthPercentage> {} = Size::zero(),
RotationProp rotation: Rotation {} = Rotation::Rotation0,
);

prop_extractor! {
Expand Down Expand Up @@ -1716,6 +1736,19 @@ impl Style {
self.set(ZIndex, Some(z_index))
}

pub fn rotation_0(self) -> Self {
self.rotation(Rotation::Rotation0)
}
pub fn rotation_90(self) -> Self {
self.rotation(Rotation::Rotation90)
}
pub fn rotation_180(self) -> Self {
self.rotation(Rotation::Rotation180)
}
pub fn rotation_270(self) -> Self {
self.rotation(Rotation::Rotation270)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

/// Allow the application of a function if the option exists.
/// This is useful for chaining together a bunch of optional style changes.
/// ```rust
Expand Down
39 changes: 35 additions & 4 deletions src/views/label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,23 @@ use crate::{
cosmic_text::{Attrs, AttrsList, FamilyOwned, TextLayout},
id::Id,
prop_extractor,
style::Style,
style::{FontProps, LineHeight, TextColor, TextOverflow, TextOverflowProp},
style::{Rotation, RotationProp, Style},
unit::PxPct,
view::{View, ViewData, Widget},
};
use floem_peniko::Color;
use floem_reactive::create_updater;
use floem_renderer::Renderer;
use kurbo::{Point, Rect};
use kurbo::{Affine, Point, Rect};
use taffy::tree::NodeId;

prop_extractor! {
Extracter {
color: TextColor,
text_overflow: TextOverflowProp,
line_height: LineHeight,
rotation: RotationProp,
}
}

Expand Down Expand Up @@ -226,6 +227,13 @@ impl Widget for Label {
}
let text_node = self.text_node.unwrap();

let (width, height) = match self.style.rotation() {
Rotation::Rotation0 => (width, height),
Rotation::Rotation90 => (height, width),
Rotation::Rotation180 => (width, height),
Rotation::Rotation270 => (height, width),
};

let style = Style::new().width(width).height(height).to_taffy_style();
let _ = cx.app_state_mut().taffy.set_style(text_node, style);

Expand Down Expand Up @@ -283,7 +291,13 @@ impl Widget for Label {
if width > available_width {
if self.available_width != Some(available_width) {
let mut text_layout = text_layout.clone();
text_layout.set_size(available_width, f32::MAX);
let (width, height) = match self.style.rotation() {
Rotation::Rotation0 => (available_width, f32::MAX),
Rotation::Rotation90 => (f32::MAX, available_width),
Rotation::Rotation180 => (available_width, f32::MAX),
Rotation::Rotation270 => (f32::MAX, available_width),
};
text_layout.set_size(width, height);
self.available_text_layout = Some(text_layout);
self.available_width = Some(available_width);
cx.app_state_mut().request_layout(self.id());
Expand Down Expand Up @@ -315,13 +329,30 @@ impl Widget for Label {
return;
}

let text_layout = self.text_layout.as_ref().unwrap();
let higth = text_layout.size().height as f32;
let width = text_layout.size().width as f32;
let text_node = self.text_node.unwrap();
let location = cx.app_state.taffy.layout(text_node).unwrap().location;
let point = Point::new(location.x as f64, location.y as f64);

let (x, y) = match self.style.rotation() {
Rotation::Rotation0 => (location.x, location.y),
Rotation::Rotation90 => ((location.x + higth), location.y),
Rotation::Rotation180 => ((location.x + width), (location.y + higth)),
Rotation::Rotation270 => (location.x, (location.y + width)),
};
let point = Point::new(x as f64, y as f64);

let mut affine = cx.transform.as_coeffs();
affine[0] = self.style.rotation().angle();
cx.paint_state.renderer.transform(Affine::new(affine));

if let Some(text_layout) = self.available_text_layout.as_ref() {
cx.draw_text(text_layout, point);
} else {
cx.draw_text(self.text_layout.as_ref().unwrap(), point);
}

cx.paint_state.renderer.transform(cx.transform);
}
}
16 changes: 12 additions & 4 deletions vger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,12 +404,19 @@ impl Renderer for VgerRenderer {
fn draw_text(&mut self, layout: &TextLayout, pos: impl Into<Point>) {
let mut swash_cache = SwashCache::new();
let transform = self.transform.as_coeffs();
let offset = Vec2::new(transform[4], transform[5]);

let pos: Point = pos.into();
let pos = Point::new(pos.x + transform[4], pos.y + transform[5]);
// transform[0] : Angle
let theta = std::f32::consts::TAU * transform[0] as f32 / 360.0;
let pos = Affine::rotate(-theta as f64) * pos;
// Absolute coordinate rotation
self.vger.rotate(theta);

let clip = self.clip;
for line in layout.layout_runs() {
if let Some(rect) = clip {
let y = pos.y + offset.y + line.line_y as f64;
let y = pos.y + line.line_y as f64;
if y + (line.line_height as f64) < rect.y0 {
continue;
}
Expand All @@ -418,8 +425,8 @@ impl Renderer for VgerRenderer {
}
}
'line_loop: for glyph_run in line.glyphs {
let x = glyph_run.x + pos.x as f32 + offset.x as f32;
let y = line.line_y + pos.y as f32 + offset.y as f32;
let x = glyph_run.x + pos.x as f32;
let y = line.line_y + pos.y as f32;

if let Some(rect) = clip {
if ((x + glyph_run.w) as f64) < rect.x0 {
Expand Down Expand Up @@ -463,6 +470,7 @@ impl Renderer for VgerRenderer {
}
}
}
self.vger.rotate(-theta);
}

fn draw_img(&mut self, img: Img<'_>, rect: Rect) {
Expand Down