chrome: greatly improve soft-keyboard visibility & layout handling
Some checks failed
CI / Rustfmt + Clippy (push) Has been cancelled
CI / Check (android) (push) Has been cancelled
CI / Test (Linux) (push) Has been cancelled
CI / Test (macOS) (push) Has been cancelled
CI / Test (Windows) (push) Has been cancelled
CI / rpm/deb (aarch64) (push) Has been cancelled
CI / rpm/deb (x86_64) (push) Has been cancelled
CI / macOS dmg (aarch64) (push) Has been cancelled
CI / macOS dmg (x86_64) (push) Has been cancelled
CI / Windows Installer (aarch64) (push) Has been cancelled
CI / Windows Installer (x86_64) (push) Has been cancelled
CI / Upload Artifacts to Server (push) Has been cancelled
Some checks failed
CI / Rustfmt + Clippy (push) Has been cancelled
CI / Check (android) (push) Has been cancelled
CI / Test (Linux) (push) Has been cancelled
CI / Test (macOS) (push) Has been cancelled
CI / Test (Windows) (push) Has been cancelled
CI / rpm/deb (aarch64) (push) Has been cancelled
CI / rpm/deb (x86_64) (push) Has been cancelled
CI / macOS dmg (aarch64) (push) Has been cancelled
CI / macOS dmg (x86_64) (push) Has been cancelled
CI / Windows Installer (aarch64) (push) Has been cancelled
CI / Windows Installer (x86_64) (push) Has been cancelled
CI / Upload Artifacts to Server (push) Has been cancelled
This reworks how we detect and respond to the on-screen keyboard so inputs don’t get buried and the UI doesn’t “jump”. - Add SoftKeyboardAnim + AnimState FSM for smooth IME open/close animation - Centralize logic in keyboard_visibility() with clear edge states - Animate keyboard height via animate_value_with_time instead of layer transforms - Add ChromeOptions::KeyboardVisibility flag when focused input would be occluded - Add SidebarOptions::Compact to collapse sidebar while typing - Hide mobile toolbar when keyboard is open (columns app) - Use .stick_to_bottom(true) in reply + profile editors; remove old spacer hack - Virtual keyboard toggle moved to F1 in Debug builds - Introduce SoftKeyboardContext::platform(ctx) helper - Cleanup dead/commented code and wire up soft_kb_anim_state in Chrome Result: inputs stay visible, open/close is smooth, and UI adjusts gracefully when typing. Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
@@ -40,6 +40,14 @@ pub enum SoftKeyboardContext {
|
||||
Platform { ppp: f32 },
|
||||
}
|
||||
|
||||
impl SoftKeyboardContext {
|
||||
pub fn platform(context: &egui::Context) -> Self {
|
||||
Self::Platform {
|
||||
ppp: context.pixels_per_point(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AppContext<'a> {
|
||||
pub fn soft_keyboard_rect(&self, screen_rect: Rect, ctx: SoftKeyboardContext) -> Option<Rect> {
|
||||
match ctx {
|
||||
@@ -53,8 +61,13 @@ impl<'a> AppContext<'a> {
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
use android_activity::InsetType;
|
||||
|
||||
// not sure why I need this, it seems to be consistently off by some amount of
|
||||
// pixels ?
|
||||
let fudge = 0.0;
|
||||
|
||||
let inset = self.android.get_window_insets(InsetType::Ime);
|
||||
let height = inset.bottom as f32 / ppp;
|
||||
let height = (inset.bottom as f32 / ppp) - fudge;
|
||||
skb_rect_from_screen_rect(screen_rect, height)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//use wasm_bindgen::prelude::*;
|
||||
use crate::app::NotedeckApp;
|
||||
use crate::ChromeOptions;
|
||||
use bitflags::bitflags;
|
||||
use eframe::CreationContext;
|
||||
use egui::{vec2, Button, Color32, Label, Layout, Rect, RichText, ThemePreference, Widget};
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
@@ -25,6 +26,10 @@ pub struct Chrome {
|
||||
active: i32,
|
||||
options: ChromeOptions,
|
||||
apps: Vec<NotedeckApp>,
|
||||
|
||||
/// The state of the soft keyboard animation
|
||||
soft_kb_anim_state: AnimState,
|
||||
|
||||
pub repaint_causes: HashMap<egui::RepaintCause, u64>,
|
||||
}
|
||||
|
||||
@@ -37,6 +42,14 @@ pub enum ChromePanelAction {
|
||||
Profile(notedeck::enostr::Pubkey),
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct SidebarOptions: u8 {
|
||||
const Compact = 1 << 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl ChromePanelAction {
|
||||
fn columns_navigate(ctx: &mut AppContext, chrome: &mut Chrome, route: notedeck_columns::Route) {
|
||||
chrome.switch_to_columns();
|
||||
@@ -174,6 +187,7 @@ impl Chrome {
|
||||
app_ctx: &mut AppContext,
|
||||
builder: StripBuilder,
|
||||
amt_open: f32,
|
||||
amt_keyboard_open: f32,
|
||||
) -> Option<ChromePanelAction> {
|
||||
let mut got_action: Option<ChromePanelAction> = None;
|
||||
|
||||
@@ -204,9 +218,17 @@ impl Chrome {
|
||||
self.topdown_sidebar(ui, app_ctx.i18n);
|
||||
})
|
||||
});
|
||||
|
||||
vstrip.cell(|ui| {
|
||||
ui.with_layout(Layout::bottom_up(egui::Align::Center), |ui| {
|
||||
if let Some(action) = bottomup_sidebar(self, app_ctx, ui) {
|
||||
let options = if amt_keyboard_open > 0.0 {
|
||||
SidebarOptions::Compact
|
||||
} else {
|
||||
SidebarOptions::default()
|
||||
};
|
||||
if let Some(action) =
|
||||
bottomup_sidebar(self, app_ctx, ui, options)
|
||||
{
|
||||
got_action = Some(action);
|
||||
}
|
||||
});
|
||||
@@ -250,7 +272,6 @@ impl Chrome {
|
||||
.animate_bool(open_id, self.options.contains(ChromeOptions::IsOpen))
|
||||
* side_panel_width
|
||||
}
|
||||
|
||||
/// Show the side menu or bar, depending on if we're on a narrow
|
||||
/// or wide screen.
|
||||
///
|
||||
@@ -259,52 +280,54 @@ impl Chrome {
|
||||
fn show(&mut self, ctx: &mut AppContext, ui: &mut egui::Ui) -> Option<ChromePanelAction> {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
|
||||
if ctx.args.options.contains(NotedeckOptions::Debug)
|
||||
&& ui.ctx().input(|i| i.key_pressed(egui::Key::Backtick))
|
||||
{
|
||||
self.options.toggle(ChromeOptions::VirtualKeyboard);
|
||||
}
|
||||
|
||||
let amt_open = self.amount_open(ui);
|
||||
let r = self.panel(ctx, StripBuilder::new(ui), amt_open);
|
||||
let skb_anim =
|
||||
keyboard_visibility(ui, ctx, &mut self.options, &mut self.soft_kb_anim_state);
|
||||
|
||||
let skb_ctx = if self.options.contains(ChromeOptions::VirtualKeyboard) {
|
||||
SoftKeyboardContext::Virtual
|
||||
let virtual_keyboard = self.options.contains(ChromeOptions::VirtualKeyboard);
|
||||
let keyboard_height = if self.options.contains(ChromeOptions::KeyboardVisibility) {
|
||||
skb_anim.anim_height
|
||||
} else {
|
||||
SoftKeyboardContext::Platform {
|
||||
ppp: ui.ctx().pixels_per_point(),
|
||||
}
|
||||
0.0
|
||||
};
|
||||
|
||||
// move screen up if virtual keyboard intersects with input_rect
|
||||
let screen_rect = ui.ctx().screen_rect();
|
||||
let mut keyboard_height = 0.0;
|
||||
if let Some(vkb_rect) = ctx.soft_keyboard_rect(screen_rect, skb_ctx.clone()) {
|
||||
if let SoftKeyboardContext::Virtual = skb_ctx {
|
||||
virtual_keyboard_ui(ui, vkb_rect);
|
||||
// if the soft keyboard is open, shrink the chrome contents
|
||||
let mut action: Option<ChromePanelAction> = None;
|
||||
// build a strip to carve out the soft keyboard inset
|
||||
StripBuilder::new(ui)
|
||||
.size(Size::remainder())
|
||||
.size(Size::exact(keyboard_height))
|
||||
.vertical(|mut strip| {
|
||||
// the actual content, shifted up because of the soft keyboard
|
||||
strip.cell(|ui| {
|
||||
action = self.panel(ctx, StripBuilder::new(ui), amt_open, keyboard_height);
|
||||
});
|
||||
|
||||
// the filler space taken up by the soft keyboard
|
||||
strip.cell(|ui| {
|
||||
// keyboard-visibility virtual keyboard
|
||||
if virtual_keyboard && keyboard_height > 0.0 {
|
||||
tracing::debug!("got here");
|
||||
virtual_keyboard_ui(ui, ui.available_rect_before_wrap())
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// hovering virtual keyboard
|
||||
if virtual_keyboard {
|
||||
if let Some(mut kb_rect) = skb_anim.skb_rect {
|
||||
let kb_height = if self.options.contains(ChromeOptions::KeyboardVisibility) {
|
||||
keyboard_height
|
||||
} else {
|
||||
400.0
|
||||
};
|
||||
kb_rect.min.y = kb_rect.max.y - kb_height;
|
||||
tracing::debug!("hovering virtual kb_height:{keyboard_height} kb_rect:{kb_rect}");
|
||||
virtual_keyboard_ui(ui, kb_rect)
|
||||
}
|
||||
if let Some(input_rect) = notedeck_ui::input_rect(ui) {
|
||||
if input_rect.intersects(vkb_rect) {
|
||||
tracing::debug!("screen:{screen_rect} skb:{vkb_rect}");
|
||||
keyboard_height = vkb_rect.height();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// clear last input box position state
|
||||
notedeck_ui::clear_input_rect(ui);
|
||||
}
|
||||
|
||||
let anim_height =
|
||||
ui.ctx()
|
||||
.animate_value_with_time(egui::Id::new("keyboard_anim"), keyboard_height, 0.1);
|
||||
if anim_height > 0.0 {
|
||||
ui.ctx().transform_layer_shapes(
|
||||
ui.layer_id(),
|
||||
egui::emath::TSTransform::from_translation(egui::Vec2::new(0.0, -anim_height)),
|
||||
);
|
||||
}
|
||||
|
||||
r
|
||||
action
|
||||
}
|
||||
|
||||
fn topdown_sidebar(&mut self, ui: &mut egui::Ui, i18n: &mut Localization) {
|
||||
@@ -652,38 +675,6 @@ fn pfp_button(ctx: &mut AppContext, ui: &mut egui::Ui) -> egui::Response {
|
||||
ui.put(helper.get_animation_rect(), &mut widget);
|
||||
|
||||
helper.take_animation_response()
|
||||
|
||||
// let selected = ctx.accounts.cache.selected();
|
||||
|
||||
// pfp_resp.context_menu(|ui| {
|
||||
// for (pk, account) in &ctx.accounts.cache {
|
||||
// let profile = ctx.ndb.get_profile_by_pubkey(&txn, pk).ok();
|
||||
// let is_selected = *pk == selected.key.pubkey;
|
||||
// let has_nsec = account.key.secret_key.is_some();
|
||||
|
||||
// let profile_peview_view = {
|
||||
// let max_size = egui::vec2(ui.available_width(), 77.0);
|
||||
// let resp = ui.allocate_response(max_size, egui::Sense::click());
|
||||
// ui.allocate_new_ui(UiBuilder::new().max_rect(resp.rect), |ui| {
|
||||
// ui.add(
|
||||
// &mut ProfilePic::new(ctx.img_cache, get_profile_url(profile.as_ref()))
|
||||
// .size(24.0),
|
||||
// )
|
||||
// })
|
||||
// };
|
||||
|
||||
// // if let Some(op) = profile_peview_view {
|
||||
// // return_op = Some(match op {
|
||||
// // ProfilePreviewAction::SwitchTo => AccountsViewResponse::SelectAccount(*pk),
|
||||
// // ProfilePreviewAction::RemoveAccount => AccountsViewResponse::RemoveAccount(*pk),
|
||||
// // });
|
||||
// // }
|
||||
// }
|
||||
// // if ui.menu_image_button(image, add_contents).clicked() {
|
||||
// // // ui.ctx().copy_text(url.to_owned());
|
||||
// // ui.close_menu();
|
||||
// // }
|
||||
// });
|
||||
}
|
||||
|
||||
/// The section of the chrome sidebar that starts at the
|
||||
@@ -692,10 +683,23 @@ fn bottomup_sidebar(
|
||||
chrome: &mut Chrome,
|
||||
ctx: &mut AppContext,
|
||||
ui: &mut egui::Ui,
|
||||
options: SidebarOptions,
|
||||
) -> Option<ChromePanelAction> {
|
||||
ui.add_space(8.0);
|
||||
|
||||
let pfp_resp = pfp_button(ctx, ui).on_hover_cursor(egui::CursorIcon::PointingHand);
|
||||
|
||||
// we skip this whole function in compact mode
|
||||
if options.contains(SidebarOptions::Compact) {
|
||||
return if pfp_resp.clicked() {
|
||||
Some(ChromePanelAction::Profile(
|
||||
ctx.accounts.get_selected_account().key.pubkey,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
|
||||
let accounts_resp = accounts_button(ui).on_hover_cursor(egui::CursorIcon::PointingHand);
|
||||
let settings_resp = settings_button(ui).on_hover_cursor(egui::CursorIcon::PointingHand);
|
||||
|
||||
@@ -956,3 +960,218 @@ fn virtual_keyboard_ui(ui: &mut egui::Ui, rect: egui::Rect) {
|
||||
.response
|
||||
});
|
||||
}
|
||||
|
||||
struct SoftKeyboardAnim {
|
||||
skb_rect: Option<Rect>,
|
||||
anim_height: f32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Default, Clone, Eq, PartialEq, Debug)]
|
||||
enum AnimState {
|
||||
/// It finished opening
|
||||
Opened,
|
||||
|
||||
/// We started to open
|
||||
StartOpen,
|
||||
|
||||
/// We started to close
|
||||
StartClose,
|
||||
|
||||
/// We finished openning
|
||||
FinishedOpen,
|
||||
|
||||
/// We finished to close
|
||||
FinishedClose,
|
||||
|
||||
/// It finished closing
|
||||
#[default]
|
||||
Closed,
|
||||
|
||||
/// We are animating towards open
|
||||
Opening,
|
||||
|
||||
/// We are animating towards close
|
||||
Closing,
|
||||
}
|
||||
|
||||
impl SoftKeyboardAnim {
|
||||
/// Advance the FSM based on current (anim_height) vs target (skb_rect.height()).
|
||||
/// Start*/Finished* are one-tick edge states used for signaling.
|
||||
fn changed(&self, state: AnimState) -> AnimState {
|
||||
const EPS: f32 = 0.01;
|
||||
|
||||
let target = self.skb_rect.map_or(0.0, |r| r.height());
|
||||
let current = self.anim_height;
|
||||
|
||||
let done = (current - target).abs() <= EPS;
|
||||
let going_up = target > current + EPS;
|
||||
let going_down = current > target + EPS;
|
||||
let target_is_closed = target <= EPS;
|
||||
|
||||
match state {
|
||||
// Resting states: emit a Start* edge only when a move is requested,
|
||||
// and pick direction by the sign of (target - current).
|
||||
AnimState::Opened => {
|
||||
if done {
|
||||
AnimState::Opened
|
||||
} else if going_up {
|
||||
AnimState::StartOpen
|
||||
} else {
|
||||
AnimState::StartClose
|
||||
}
|
||||
}
|
||||
AnimState::Closed => {
|
||||
if done {
|
||||
AnimState::Closed
|
||||
} else if going_up {
|
||||
AnimState::StartOpen
|
||||
} else {
|
||||
AnimState::StartClose
|
||||
}
|
||||
}
|
||||
|
||||
// Edge → flow
|
||||
AnimState::StartOpen => AnimState::Opening,
|
||||
AnimState::StartClose => AnimState::Closing,
|
||||
|
||||
// Flow states: finish when we hit the target; if the target jumps across,
|
||||
// emit the opposite Start* to signal a reversal.
|
||||
AnimState::Opening => {
|
||||
if done {
|
||||
if target_is_closed {
|
||||
AnimState::FinishedClose
|
||||
} else {
|
||||
AnimState::FinishedOpen
|
||||
}
|
||||
} else if going_down {
|
||||
// target moved below current mid-flight → reversal
|
||||
AnimState::StartClose
|
||||
} else {
|
||||
AnimState::Opening
|
||||
}
|
||||
}
|
||||
AnimState::Closing => {
|
||||
if done {
|
||||
if target_is_closed {
|
||||
AnimState::FinishedClose
|
||||
} else {
|
||||
AnimState::FinishedOpen
|
||||
}
|
||||
} else if going_up {
|
||||
// target moved above current mid-flight → reversal
|
||||
AnimState::StartOpen
|
||||
} else {
|
||||
AnimState::Closing
|
||||
}
|
||||
}
|
||||
|
||||
// Finish edges collapse to the stable resting states on the next tick.
|
||||
AnimState::FinishedOpen => AnimState::Opened,
|
||||
AnimState::FinishedClose => AnimState::Closed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// How "open" the softkeyboard is. This is an animated value
|
||||
fn soft_keyboard_anim(
|
||||
ui: &mut egui::Ui,
|
||||
ctx: &mut AppContext,
|
||||
chrome_options: &mut ChromeOptions,
|
||||
) -> SoftKeyboardAnim {
|
||||
let skb_ctx = if chrome_options.contains(ChromeOptions::VirtualKeyboard) {
|
||||
SoftKeyboardContext::Virtual
|
||||
} else {
|
||||
SoftKeyboardContext::Platform {
|
||||
ppp: ui.ctx().pixels_per_point(),
|
||||
}
|
||||
};
|
||||
|
||||
// move screen up if virtual keyboard intersects with input_rect
|
||||
let screen_rect = ui.ctx().screen_rect();
|
||||
let mut skb_rect: Option<Rect> = None;
|
||||
|
||||
let keyboard_height =
|
||||
if let Some(vkb_rect) = ctx.soft_keyboard_rect(screen_rect, skb_ctx.clone()) {
|
||||
skb_rect = Some(vkb_rect);
|
||||
vkb_rect.height()
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let anim_height =
|
||||
ui.ctx()
|
||||
.animate_value_with_time(egui::Id::new("keyboard_anim"), keyboard_height, 0.1);
|
||||
|
||||
SoftKeyboardAnim {
|
||||
anim_height,
|
||||
skb_rect,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_toggle_virtual_keyboard(
|
||||
ctx: &egui::Context,
|
||||
options: NotedeckOptions,
|
||||
chrome_options: &mut ChromeOptions,
|
||||
) {
|
||||
// handle virtual keyboard toggle here because why not
|
||||
if options.contains(NotedeckOptions::Debug) && ctx.input(|i| i.key_pressed(egui::Key::F1)) {
|
||||
chrome_options.toggle(ChromeOptions::VirtualKeyboard);
|
||||
}
|
||||
}
|
||||
|
||||
/// All the logic which handles our keyboard visibility
|
||||
fn keyboard_visibility(
|
||||
ui: &mut egui::Ui,
|
||||
ctx: &mut AppContext,
|
||||
options: &mut ChromeOptions,
|
||||
soft_kb_anim_state: &mut AnimState,
|
||||
) -> SoftKeyboardAnim {
|
||||
try_toggle_virtual_keyboard(ui.ctx(), ctx.args.options, options);
|
||||
|
||||
let soft_kb_anim = soft_keyboard_anim(ui, ctx, options);
|
||||
|
||||
let prev_state = *soft_kb_anim_state;
|
||||
let current_state = soft_kb_anim.changed(prev_state);
|
||||
*soft_kb_anim_state = current_state;
|
||||
|
||||
if prev_state != current_state {
|
||||
tracing::debug!("soft kb state {prev_state:?} -> {current_state:?}");
|
||||
}
|
||||
|
||||
match current_state {
|
||||
// we finished
|
||||
AnimState::FinishedOpen => {}
|
||||
|
||||
// on first open, we setup our scroll target
|
||||
AnimState::StartOpen => {
|
||||
// when we first open the keyboard, check to see if the target soft
|
||||
// keyboard rect (the height at full open) intersects with any
|
||||
// input response rects from last frame
|
||||
//
|
||||
// If we do, then we set a bit that we need keyboard visibility.
|
||||
// We will use this bit to resize the screen based on the soft
|
||||
// keyboard animation state
|
||||
if let Some(skb_rect) = soft_kb_anim.skb_rect {
|
||||
if let Some(input_rect) = notedeck_ui::input_rect(ui) {
|
||||
options.set(
|
||||
ChromeOptions::KeyboardVisibility,
|
||||
input_rect.intersects(skb_rect),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimState::FinishedClose => {
|
||||
// clear last input box position state
|
||||
notedeck_ui::clear_input_rect(ui);
|
||||
}
|
||||
|
||||
AnimState::Closing => {}
|
||||
AnimState::Opened => {}
|
||||
AnimState::Closed => {}
|
||||
AnimState::Opening => {}
|
||||
AnimState::StartClose => {}
|
||||
};
|
||||
|
||||
soft_kb_anim
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ bitflags! {
|
||||
|
||||
/// Repaint debug
|
||||
const RepaintDebug = 1 << 3;
|
||||
|
||||
/// We need soft keyboard visibility
|
||||
const KeyboardVisibility = 1 << 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -644,10 +644,20 @@ fn render_damus_mobile(
|
||||
|
||||
let active_col = app.columns_mut(app_ctx.i18n, app_ctx.accounts).selected as usize;
|
||||
let mut app_action: Option<AppAction> = None;
|
||||
// don't show toolbar if soft keyboard is open
|
||||
let skb_rect = app_ctx.soft_keyboard_rect(
|
||||
ui.ctx().screen_rect(),
|
||||
notedeck::SoftKeyboardContext::platform(ui.ctx()),
|
||||
);
|
||||
let toolbar_height = if skb_rect.is_none() {
|
||||
Damus::toolbar_height()
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
StripBuilder::new(ui)
|
||||
.size(Size::remainder()) // top cell
|
||||
.size(Size::exact(Damus::toolbar_height())) // bottom cell
|
||||
.size(Size::exact(toolbar_height)) // bottom cell
|
||||
.vertical(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
@@ -678,17 +688,22 @@ fn render_damus_mobile(
|
||||
hovering_post_button(ui, app, app_ctx, rect);
|
||||
});
|
||||
|
||||
strip.cell(|ui| {
|
||||
strip.cell(|ui| 'brk: {
|
||||
if toolbar_height <= 0.0 {
|
||||
break 'brk;
|
||||
}
|
||||
|
||||
let unseen_notif = unseen_notification(
|
||||
app,
|
||||
app_ctx.ndb,
|
||||
app_ctx.accounts.get_selected_account().key.pubkey,
|
||||
);
|
||||
|
||||
let resp = toolbar(ui, unseen_notif);
|
||||
|
||||
if let Some(action) = resp {
|
||||
action.process(app, app_ctx);
|
||||
if skb_rect.is_none() {
|
||||
let resp = toolbar(ui, unseen_notif);
|
||||
if let Some(action) = resp {
|
||||
action.process(app, app_ctx);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -55,6 +55,7 @@ impl<'a, 'd> PostReplyView<'a, 'd> {
|
||||
pub fn show(&mut self, ui: &mut egui::Ui) -> PostResponse {
|
||||
ScrollArea::vertical()
|
||||
.id_salt(self.scroll_id)
|
||||
.stick_to_bottom(true)
|
||||
.show(ui, |ui| self.show_internal(ui))
|
||||
.inner
|
||||
}
|
||||
@@ -121,7 +122,7 @@ impl<'a, 'd> PostReplyView<'a, 'd> {
|
||||
// large and things start breaking. I think this is an ok
|
||||
// solution but there could be a better one.
|
||||
//
|
||||
ui.add_space(500.0);
|
||||
//ui.add_space(500.0);
|
||||
|
||||
post_response
|
||||
})
|
||||
|
||||
@@ -37,6 +37,7 @@ impl<'a> EditProfileView<'a> {
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) -> bool {
|
||||
ScrollArea::vertical()
|
||||
.id_salt(EditProfileView::scroll_id())
|
||||
.stick_to_bottom(true)
|
||||
.show(ui, |ui| {
|
||||
banner(ui, self.state.banner(), 188.0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user