From b1780ab6eda33fcc86c7f0ede80074adb0409756 Mon Sep 17 00:00:00 2001 From: Julian Montes de Oca Date: Thu, 10 Oct 2024 22:56:25 +0800 Subject: [PATCH 1/3] Introduce Dock for desktop UI --- src/app.rs | 17 +- src/home/home_screen.rs | 26 +- src/home/light_themed_dock.rs | 298 ++++++++++++++++++ src/home/main_desktop_ui.rs | 260 +++++++++++++++ .../{main_content.rs => main_mobile_ui.rs} | 13 +- src/home/mod.rs | 8 +- src/home/room_preview.rs | 86 ++--- src/home/room_screen.rs | 54 ++-- src/home/rooms_list.rs | 15 +- src/home/rooms_sidebar.rs | 2 +- 10 files changed, 683 insertions(+), 96 deletions(-) create mode 100644 src/home/light_themed_dock.rs create mode 100644 src/home/main_desktop_ui.rs rename src/home/{main_content.rs => main_mobile_ui.rs} (86%) diff --git a/src/app.rs b/src/app.rs index 04fae7b9..d062ea93 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,7 @@ use makepad_widgets::*; use matrix_sdk::ruma::OwnedRoomId; use crate::{ - home::rooms_list::RoomListAction, + home::{main_desktop_ui::RoomsPanelAction, rooms_list::RoomListAction}, verification::VerificationAction, verification_modal::{VerificationModalAction, VerificationModalWidgetRefExt}, }; @@ -13,9 +13,7 @@ live_design! { import makepad_draw::shader::std::*; import crate::shared::styles::*; - import crate::shared::clickable_view::ClickableView; import crate::home::home_screen::HomeScreen; - import crate::home::room_screen::RoomScreen; import crate::profile::my_profile_screen::MyProfileScreen; import crate::verification_modal::VerificationModal; @@ -189,6 +187,19 @@ impl MatchEvent for App { RoomListAction::None => { } } + match action.as_widget_action().cast() { + RoomsPanelAction::RoomFocused(room_id) => { + self.app_state.rooms_panel.selected_room = Some(SelectedRoom { + id: room_id.clone(), + name: None + }); + } + RoomsPanelAction::FocusNone => { + self.app_state.rooms_panel.selected_room = None; + } + _ => { } + } + // `VerificationAction`s come from a background thread, so they are NOT widget actions. // Therefore, we cannot use `as_widget_action().cast()` to match them. match action.downcast_ref() { diff --git a/src/home/home_screen.rs b/src/home/home_screen.rs index e72c3ad3..dc935da8 100644 --- a/src/home/home_screen.rs +++ b/src/home/home_screen.rs @@ -5,11 +5,13 @@ live_design! { import makepad_widgets::theme_desktop_dark::*; import makepad_draw::shader::std::*; - import crate::home::main_content::MainContent; + import crate::home::main_mobile_ui::MainMobileUI; import crate::home::rooms_sidebar::RoomsSideBar; import crate::home::spaces_dock::SpacesDock; import crate::shared::styles::*; import crate::shared::adaptive_view::AdaptiveView; + import crate::shared::search_bar::SearchBar; + import crate::home::main_desktop_ui::MainDesktopUI; NavigationWrapper = {{NavigationWrapper}} { view_stack = {} @@ -24,10 +26,15 @@ live_design! { width: Fill, height: Fill padding: 0, margin: 0, align: {x: 0.0, y: 0.0} flow: Right - + spaces = {} - rooms_sidebar = {} - main_content = {} + + { + flow: Down + width: Fill, height: Fill + {} + {} + } } Mobile = { @@ -47,11 +54,11 @@ live_design! { sidebar = {} spaces = {} } - + main_content_view = { width: Fill, height: Fill body = { - main_content = {} + main_content = {} } } } @@ -63,7 +70,7 @@ live_design! { #[derive(Live, LiveHook, Widget)] pub struct NavigationWrapper { #[deref] - view: View + view: View, } impl Widget for NavigationWrapper { @@ -77,7 +84,8 @@ impl Widget for NavigationWrapper { } impl MatchEvent for NavigationWrapper { - fn handle_actions(&mut self, cx: &mut Cx, actions:&Actions) { - self.stack_navigation(id!(view_stack)).handle_stack_view_actions(cx, actions); + fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) { + self.stack_navigation(id!(view_stack)) + .handle_stack_view_actions(cx, actions); } } diff --git a/src/home/light_themed_dock.rs b/src/home/light_themed_dock.rs new file mode 100644 index 00000000..2dd60a49 --- /dev/null +++ b/src/home/light_themed_dock.rs @@ -0,0 +1,298 @@ +use makepad_widgets::*; + +live_design! { + import makepad_widgets::base::*; + import makepad_widgets::theme_desktop_dark::*; + import makepad_draw::shader::std::*; + + import crate::shared::styles::*; + + Splitter = { + draw_splitter: { + uniform border_radius: 1.0 + uniform splitter_pad: 1.0 + uniform splitter_grabber: 110.0 + + instance pressed: 0.0 + instance hover: 0.0 + + fn pixel(self) -> vec4 { + let sdf = Sdf2d::viewport(self.pos * self.rect_size); + sdf.clear(#ef); + + if self.is_vertical > 0.5 { + sdf.box( + self.splitter_pad, + self.rect_size.y * 0.5 - self.splitter_grabber * 0.5, + self.rect_size.x - 2.0 * self.splitter_pad, + self.splitter_grabber, + self.border_radius + ); + } + else { + sdf.box( + self.rect_size.x * 0.5 - self.splitter_grabber * 0.5, + self.splitter_pad, + self.splitter_grabber, + self.rect_size.y - 2.0 * self.splitter_pad, + self.border_radius + ); + } + return sdf.fill_keep(mix( + THEME_COLOR_D_HIDDEN, + mix( + THEME_COLOR_CTRL_SCROLLBAR_HOVER, + THEME_COLOR_CTRL_SCROLLBAR_HOVER * 1.2, + self.pressed + ), + self.hover + )); + } + } + split_bar_size: (THEME_SPLITTER_SIZE) + min_horizontal: (THEME_SPLITTER_MIN_HORIZONTAL) + max_horizontal: (THEME_SPLITTER_MAX_HORIZONTAL) + min_vertical: (THEME_SPLITTER_MIN_VERTICAL) + max_vertical: (THEME_SPLITTER_MAX_VERTICAL) + + animator: { + hover = { + default: off + off = { + from: {all: Forward {duration: 0.1}} + apply: { + draw_splitter: {pressed: 0.0, hover: 0.0} + } + } + + on = { + from: { + all: Forward {duration: 0.1} + state_down: Forward {duration: 0.01} + } + apply: { + draw_splitter: { + pressed: 0.0, + hover: [{time: 0.0, value: 1.0}], + } + } + } + + pressed = { + from: { all: Forward { duration: 0.1 }} + apply: { + draw_splitter: { + pressed: [{time: 0.0, value: 1.0}], + hover: 1.0, + } + } + } + } + } + } + + TabCloseButton = { + // TODO: NEEDS FOCUS STATE + height: 10.0, width: 10.0, + margin: { right: (THEME_SPACE_2), left: -3.5 }, + draw_button: { + + instance hover: float; + instance selected: float; + + fn pixel(self) -> vec4 { + let sdf = Sdf2d::viewport(self.pos * self.rect_size); + let mid = self.rect_size / 2.0; + let size = (self.hover * 0.25 + 0.5) * 0.25 * length(self.rect_size); + let min = mid - vec2(size); + let max = mid + vec2(size); + sdf.move_to(min.x, min.y); + sdf.line_to(max.x, max.y); + sdf.move_to(min.x, max.y); + sdf.line_to(max.x, min.y); + return sdf.stroke(mix( + #f, + #4, + self.hover + ), 1.0); + } + } + + animator: { + hover = { + default: off + off = { + from: {all: Forward {duration: 0.1}} + apply: { + draw_button: {hover: 0.0} + } + } + + on = { + cursor: Hand, + from: {all: Snap} + apply: { + draw_button: {hover: 1.0} + } + } + } + } + } + + Tab = { + width: Fit, height: Fill, //Fixed((THEME_TAB_HEIGHT)), + + align: {x: 0.0, y: 0.5} + padding: { } + + close_button: {} + draw_name: { + text_style: {} + instance hover: 0.0 + instance selected: 0.0 + fn get_color(self) -> vec4 { + return mix( + mix( + #x0, // THEME_COLOR_TEXT_INACTIVE, + #xf, // THEME_COLOR_TEXT_SELECTED, + self.selected + ), + THEME_COLOR_TEXT_HOVER, + self.hover + ) + } + } + + draw_bg: { + instance hover: float + instance selected: float + + fn pixel(self) -> vec4 { + let sdf = Sdf2d::viewport(self.pos * self.rect_size); + sdf.box( + -1., + -1., + self.rect_size.x + 2, + self.rect_size.y + 2, + 1. + ) + sdf.fill_keep( + mix( + (COLOR_SECONDARY) * 0.95, + (COLOR_SELECTED_PRIMARY), + self.selected + ) + ) + return sdf.result + } + } + + animator: { + hover = { + default: off + off = { + from: {all: Forward {duration: 0.2}} + apply: { + draw_bg: {hover: 0.0} + draw_name: {hover: 0.0} + } + } + + on = { + cursor: Hand, + from: {all: Forward {duration: 0.1}} + apply: { + draw_bg: {hover: [{time: 0.0, value: 1.0}]} + draw_name: {hover: [{time: 0.0, value: 1.0}]} + } + } + } + + selected = { + default: off + off = { + from: {all: Forward {duration: 0.3}} + apply: { + close_button: {draw_button: {selected: 0.0}} + draw_bg: {selected: 0.0} + draw_name: {selected: 0.0} + } + } + + on = { + from: {all: Snap} + apply: { + close_button: {draw_button: {selected: 1.0}} + draw_bg: {selected: 1.0} + draw_name: {selected: 1.0} + } + } + } + } + } + + TabBar = { + CloseableTab = {closeable:true} + PermanentTab = {closeable:false} + + draw_drag: { + draw_depth: 10 + color: #x0 + } + draw_fill: { + color: (COLOR_SECONDARY) + } + + width: Fill, height: (THEME_TAB_HEIGHT) + + scroll_bars: { + show_scroll_x: true + show_scroll_y: false + scroll_bar_x: { + draw_bar: {bar_width: 3.0} + bar_size: 4 + use_vertical_finger_scroll: true + } + } + } + + Dock = { + flow: Down, + + round_corner: { + draw_depth: 20.0 + border_radius: 20. + fn pixel(self) -> vec4 { + let pos = vec2( + mix(self.pos.x, 1.0 - self.pos.x, self.flip.x), + mix(self.pos.y, 1.0 - self.pos.y, self.flip.y) + ) + + let sdf = Sdf2d::viewport(pos * self.rect_size); + sdf.rect(-10., -10., self.rect_size.x * 2.0, self.rect_size.y * 2.0); + sdf.box( + 0.25, + 0.25, + self.rect_size.x * 2.0, + self.rect_size.y * 2.0, + 4.0 + ); + + sdf.subtract() + // sdf.fill(THEME_COLOR_BG_APP) + sdf.fill(COLOR_SECONDARY) + return sdf.result + } + } + border_size: (THEME_DOCK_BORDER_SIZE) + + padding: {left: (THEME_DOCK_BORDER_SIZE), top: 0, right: (THEME_DOCK_BORDER_SIZE), bottom: (THEME_DOCK_BORDER_SIZE)} + padding_fill: {color: (THEME_COLOR_BG_APP)} // TODO: unclear what this does + drag_quad: { + draw_depth: 10.0 + color: (mix((COLOR_SECONDARY), #FFFFFF00, pow(0.25, THEME_COLOR_CONTRAST))) + } + tab_bar: {} + splitter: {} + } +} \ No newline at end of file diff --git a/src/home/main_desktop_ui.rs b/src/home/main_desktop_ui.rs new file mode 100644 index 00000000..bae31bf0 --- /dev/null +++ b/src/home/main_desktop_ui.rs @@ -0,0 +1,260 @@ +use makepad_widgets::*; +use matrix_sdk::ruma::OwnedRoomId; +use std::collections::HashMap; + +use crate::app::{AppState, SelectedRoom}; + +use super::room_screen::RoomScreenWidgetRefExt; +live_design! { + import makepad_widgets::base::*; + import makepad_widgets::theme_desktop_dark::*; + import makepad_draw::shader::std::*; + + import crate::shared::styles::*; + import crate::home::light_themed_dock::*; + import crate::home::welcome_screen::WelcomeScreen; + import crate::home::rooms_sidebar::RoomsSideBar; + import crate::home::room_screen::RoomScreen; + + MainDesktopUI = {{MainDesktopUI}} { + dock = { + width: Fill, + height: Fill, + padding: 0, + spacing: 0, + + root = Splitter { + axis: Horizontal, + align: FromA(300.0), + a: rooms_sidebar_tab, + b: main + } + + // Not really a tab, but it needs to be one to be used in the dock + rooms_sidebar_tab = Tab { + name: "" // show no tab header + kind: rooms_sidebar + } + + main = Tabs{tabs:[home_tab], selected:0} + + home_tab = Tab { + name: "Home" + kind: welcome_screen + template: PermanentTab + } + + rooms_sidebar = {} + welcome_screen = {} + room_screen = {} + } + } +} + +#[derive(Live, LiveHook, Widget)] +pub struct MainDesktopUI { + #[deref] + view: View, + + /// The rooms that are currently open + #[rust] + open_rooms: HashMap, + + /// The tab that should be closed in the next draw event + #[rust] + tab_to_close: Option, + + /// The order in which the rooms were opened + #[rust] + room_order: Vec, +} + +impl Widget for MainDesktopUI { + fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { + self.match_event(cx, event); + self.view.handle_event(cx, event, scope); + } + + fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { + let dock = self.view.dock(id!(dock)); + let app_state = scope.data.get_mut::().unwrap(); + + // In case a room is closed, this will be the newly selected room if there is one + let mut new_selected_room: Option = None; + let mut focus_reset = false; + + if let Some(tab_id) = self.tab_to_close { + if let Some(room_id) = self.open_rooms.get(&tab_id) { + self.room_order.remove( + self.room_order.iter().position(|id| id == room_id).unwrap() + ); + + if self.open_rooms.len() > 1 { + // if the closing tab is the active one, then focus the next room + let active_room = app_state.rooms_panel.selected_room.as_ref(); + if let Some(active_room) = active_room { + if active_room.id == *room_id { + if let Some(new_focused_room_id) = self.room_order.last() { + // notify the app state about the new focused room + cx.widget_action( + self.widget_uid(), + &scope.path, + RoomsPanelAction::RoomFocused(new_focused_room_id.clone()), + ); + + // set the new selected room to be used in the current draw + new_selected_room = Some(SelectedRoom { + id: new_focused_room_id.clone(), + name: None + }); + } + } + } + } else { + // if there is no room to focus, reset the selected room in the app state + // app_state.rooms_panel.selected_room = None; + cx.widget_action( + self.widget_uid(), + &scope.path, + RoomsPanelAction::FocusNone, + ); + + focus_reset = true; + dock.select_tab(cx, live_id!(home_tab)); + } + } + dock.close_tab(cx, tab_id); + self.tab_to_close = None; + self.open_rooms.remove(&tab_id); + } + + // if the focus was not reset, then focus the new selected room or the previously selected one + if !focus_reset { + if let Some(room) = new_selected_room { + self.focus_or_create_tab(cx, &room); + } else if let Some(room) = app_state.rooms_panel.selected_room.as_ref() { + self.focus_or_create_tab(cx, room); + } + } + self.view.draw_walk(cx, scope, walk) + } +} + +impl MainDesktopUI { + /// Focuses on a room if it is already open, otherwise creates a new tab for the room + fn focus_or_create_tab(&mut self, cx: &mut Cx2d, room: &SelectedRoom) { + let dock = self.view.dock(id!(dock)); + + // if the room is already open, select its tab + let room_id_as_live_id = LiveId::from_str(&room.id.to_string()); + if self.open_rooms.contains_key(&room_id_as_live_id) { + dock.select_tab(cx, room_id_as_live_id); + return; + } + + self.open_rooms.insert(room_id_as_live_id, room.id.clone()); + + let displayed_room_name = room + .name + .clone() + .unwrap_or_else(|| format!("Room ID {}", &room.id)); + + // create a new tab for the room + let (tab_bar, _pos) = dock.find_tab_bar_of_tab(live_id!(home_tab)).unwrap(); + let kind = live_id!(room_screen); + + let result = dock.create_and_select_tab( + cx, + tab_bar, + room_id_as_live_id, + kind, + displayed_room_name.clone(), + live_id!(CloseableTab), + // `None` will insert the tab at the end + None, + ); + + // if the tab was created, set the room screen and add the room to the room order + if let Some(widget) = result { + self.room_order.push(room.id.clone()); + widget.as_room_screen().set_displayed_room( + cx, + displayed_room_name, + room.id.clone(), + ); + } + } +} + +impl MatchEvent for MainDesktopUI { + fn handle_action(&mut self, cx: &mut Cx, action: &Action) { + let dock = self.view.dock(id!(dock)); + + if let Some(action) = action.as_widget_action() { + match action.cast() { + // Whenever a tab is pressed notify the app state about it, except for the home tab + DockAction::TabWasPressed(tab_id) => { + if tab_id == live_id!(home_tab) { + cx.widget_action( + self.widget_uid(), + &HeapLiveIdPath::default(), + RoomsPanelAction::FocusNone, + ); + } else if let Some(room_id) = self.open_rooms.get(&tab_id) { + cx.widget_action( + self.widget_uid(), + &HeapLiveIdPath::default(), + RoomsPanelAction::RoomFocused(room_id.clone()), + ); + } + } + // Whenever a tab is closed, defer the close to the next event loop to prevent closing the tab while the app state + // still has the room as selected + DockAction::TabCloseWasPressed(tab_id) => { + self.tab_to_close = Some(tab_id); + self.redraw(cx); + } + // When dragging a tab, allow it to be dragged + DockAction::ShouldTabStartDrag(tab_id) => { + dock.tab_start_drag( + cx, + tab_id, + DragItem::FilePath { + path: "".to_string(), + internal_id: Some(tab_id), + }, + ); + } + // When dragging a tab, allow it to be dragged + DockAction::Drag(drag_event) => { + if drag_event.items.len() == 1 { + dock.accept_drag(cx, drag_event, DragResponse::Move); + } + } + // When dropping a tab, move it to the new position + DockAction::Drop(drop_event) => { + if let DragItem::FilePath { + path: _, + internal_id, + } = &drop_event.items[0] + { + // from inside the dock, otherwise it's an external file + if let Some(internal_id) = internal_id { + dock.drop_move(cx, drop_event.abs, *internal_id); + } + } + } + _ => (), + } + } + } +} + +#[derive(Clone, DefaultNone, Debug)] +pub enum RoomsPanelAction { + None, + /// Notifies that a room was focused + RoomFocused(OwnedRoomId), + /// Resets the focus on the rooms panel + FocusNone, +} diff --git a/src/home/main_content.rs b/src/home/main_mobile_ui.rs similarity index 86% rename from src/home/main_content.rs rename to src/home/main_mobile_ui.rs index faf7356d..d515dc63 100644 --- a/src/home/main_content.rs +++ b/src/home/main_mobile_ui.rs @@ -10,13 +10,12 @@ live_design! { import makepad_draw::shader::std::*; import crate::shared::styles::*; - import crate::shared::search_bar::SearchBar; import crate::shared::cached_widget::CachedWidget; import crate::home::room_screen::RoomScreen; import crate::home::welcome_screen::WelcomeScreen; - MainContent = {{MainContent}} { + MainMobileUI = {{MainMobileUI}} { width: Fill, height: Fill flow: Down, show_bg: true @@ -25,32 +24,28 @@ live_design! { } align: {x: 0.0, y: 0.5} - {} welcome = {} rooms = { align: {x: 0.5, y: 0.5} width: Fill, height: Fill - { - room_screen = {} - } + room_screen = {} } } } #[derive(Live, LiveHook, Widget)] -pub struct MainContent { +pub struct MainMobileUI { #[deref] view: View, } -impl Widget for MainContent { +impl Widget for MainMobileUI { fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { self.view.handle_event(cx, event, scope); } fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { - // log!("Redrawing MainContent"); let app_state = scope.data.get::().unwrap(); if let Some(room) = app_state.rooms_panel.selected_room.as_ref() { diff --git a/src/home/mod.rs b/src/home/mod.rs index f4e0a250..6e03136d 100644 --- a/src/home/mod.rs +++ b/src/home/mod.rs @@ -1,13 +1,15 @@ use makepad_widgets::Cx; pub mod home_screen; -pub mod main_content; +pub mod main_mobile_ui; +pub mod main_desktop_ui; pub mod room_preview; pub mod room_screen; pub mod rooms_list; pub mod rooms_sidebar; pub mod spaces_dock; pub mod welcome_screen; +pub mod light_themed_dock; pub fn live_design(cx: &mut Cx) { home_screen::live_design(cx); @@ -15,7 +17,9 @@ pub fn live_design(cx: &mut Cx) { room_preview::live_design(cx); room_screen::live_design(cx); rooms_sidebar::live_design(cx); - main_content::live_design(cx); + main_mobile_ui::live_design(cx); + main_desktop_ui::live_design(cx); spaces_dock::live_design(cx); welcome_screen::live_design(cx); + light_themed_dock::live_design(cx); } diff --git a/src/home/room_preview.rs b/src/home/room_preview.rs index cb559e39..4d0f6d2a 100644 --- a/src/home/room_preview.rs +++ b/src/home/room_preview.rs @@ -2,7 +2,8 @@ use makepad_widgets::*; use crate::{ shared::{ - adaptive_view::{AdaptiveViewWidgetExt, DisplayContext}, avatar::AvatarWidgetExt, + adaptive_view::{AdaptiveViewWidgetExt, DisplayContext}, + avatar::AvatarWidgetExt, html_or_plaintext::HtmlOrPlaintextWidgetExt, }, utils::{self, relative_format}, @@ -101,7 +102,7 @@ live_design! { } } } - + RoomPreview = {{RoomPreview}} { // Wraps the RoomPreviewContent in an AdaptiveView // to change the displayed content (and its layout) based on the available space in the sidebar. @@ -155,12 +156,10 @@ impl LiveHook for RoomPreview { // Adapt the preview based on the available space. self.view .adaptive_view(id!(adaptive_preview)) - .set_variant_selector(cx, |_cx, parent_size| { - match parent_size.x { - x if x <= 100. => live_id!(OnlyIcon), - x if x <= 250. => live_id!(IconAndName), - _ => live_id!(FullPreview), - } + .set_variant_selector(cx, |_cx, parent_size| match parent_size.x { + x if x <= 100. => live_id!(OnlyIcon), + x if x <= 250. => live_id!(IconAndName), + _ => live_id!(FullPreview), }); } } @@ -242,7 +241,7 @@ impl Widget for RoomPreviewContent { // Mobile doesn't have a selected state. Always use the default colors. // We call the update in case the app was resized from desktop to mobile while the room was selected. // This can be optimized by only calling this when the app is resized. - self.update_preview_colors(cx, false); + self.update_preview_colors(cx, false); } } self.view.draw_walk(cx, scope, walk) @@ -279,44 +278,51 @@ impl RoomPreviewContent { ), ); - self.view.label(id!(room_name)).apply_over( - cx, - live!( + // We check that the UI elements exist to avoid unnecessary updates, and prevent error logs. + if !self.view.label(id!(room_name)).is_empty() { + self.view.label(id!(room_name)).apply_over( + cx, + live!( draw_text: { color: (room_name_color) } - ), - ); + ), + ); + } - self.view.label(id!(timestamp)).apply_over( - cx, - live!( - draw_text: { - color: (timestamp_color) - } - ), - ); + if !self.view.label(id!(timestamp)).is_empty() { + self.view.label(id!(timestamp)).apply_over( + cx, + live!( + draw_text: { + color: (timestamp_color) + } + ), + ); + } - self.html_or_plaintext(id!(latest_message)).apply_over( - cx, - live!( - html_view = { - html = { - font_color: (message_text_color), - draw_normal: { color: (message_text_color) }, - draw_italic: { color: (message_text_color) }, - draw_bold: { color: (message_text_color) }, - draw_bold_italic: { color: (message_text_color) }, - } + if !self.view.html_or_plaintext(id!(latest_message)).is_empty() { + self.view.html_or_plaintext(id!(latest_message)).apply_over( + cx, + live!( + html_view = { + html = { + font_color: (message_text_color), + draw_normal: { color: (message_text_color) }, + draw_italic: { color: (message_text_color) }, + draw_bold: { color: (message_text_color) }, + draw_bold_italic: { color: (message_text_color) }, } - plaintext_view = { - pt_label = { - draw_text: { - color: (message_text_color) - } + } + plaintext_view = { + pt_label = { + draw_text: { + color: (message_text_color) } } - ), - ); + } + ), + ); + } } } diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index 37aaa5fd..a0023a64 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -690,27 +690,6 @@ live_design! { } flow: Down, spacing: 0.0 - - tab_title = { - width: Fit, height: Fit, - align: {x: 0.0, y: 0.5}, - margin: {top: 10.0} - padding: 10. - show_bg: true - draw_bg: { - color: (COLOR_PRIMARY) - } - room_name =