Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
28 changes: 27 additions & 1 deletion src/android/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ pub struct Pointer {
}

type Launch = Box<dyn FnOnce(&mut App) + Send>;
/// Applied to the `Application` before `run_embedded`, so a host can register things
/// that must exist before the first frame — an asset source, most importantly.
type Configure = Box<dyn FnOnce(Application) -> Application + Send>;

/// Set by [`start_with_assets`], consumed when the app is built.
static CONFIGURE: Mutex<Option<Configure>> = Mutex::new(None);

/// GPUI's application, owned by the render thread.
struct HostApp {
Expand All @@ -145,6 +151,23 @@ fn wake_render_thread() {
}
}

/// Like [`start`], but also registers an [`gpui::AssetSource`] on the `Application`
/// GPUI builds on the render thread.
///
/// `Application::with_assets` consumes the builder before `run_embedded`, so a host that
/// only gets `&mut App` in its launch closure has no way to install one — anything
/// resolved through the asset source (icons, SVGs) would silently come back empty.
pub fn start_with_assets<A, F>(assets: A, launch: F)
where
A: gpui::AssetSource,
F: FnOnce(&mut App) + Send + 'static,
{
*CONFIGURE.lock().expect("poisoned") = Some(Box::new(move |application: Application| {
application.with_assets(assets)
}));
start(launch);
}

/// Start the render thread. Idempotent — later calls are no-ops, which is what makes
/// an Activity recreation cheap: the thread, the platform and the GPUI `App` all survive.
///
Expand Down Expand Up @@ -305,8 +328,11 @@ fn on_surface_created(
// Without an `AndroidApp` to drive, `AndroidPlatform::run` invokes the
// callback immediately and returns — the shape `run_embedded` expects.
// The handle is what keeps the `App` alive afterwards.
let application =
let mut application =
Application::with_platform(SharedPlatform::new(Arc::clone(platform)).into_rc());
if let Some(configure) = CONFIGURE.lock().expect("poisoned").take() {
application = configure(application);
}
app.handle = Some(application.run_embedded(launch));
}
}
Expand Down
39 changes: 36 additions & 3 deletions src/android/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ use anyhow::Result;
use futures::channel::oneshot;
use gpui::{
Action, ActivityGuard, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle,
ForegroundExecutor, KeybindingKeystroke, Keymap, Keystroke, Menu, MenuItem, PathPromptOptions,
Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem,
PlatformWindow, Task, ThermalState, WindowAppearance, WindowParams,
ForegroundExecutor, GestureTuning, KeybindingKeystroke, Keymap, Keystroke, Menu, MenuItem,
PathPromptOptions, Platform, PlatformDisplay, PlatformGestures, PlatformKeyboardLayout,
PlatformKeyboardMapper, PlatformTextSystem, PlatformWindow, ScrollPhysics, Task, ThermalState,
WindowAppearance, WindowParams,
};
use gpui_wgpu::CosmicTextSystem;
use parking_lot::Mutex;
Expand Down Expand Up @@ -952,6 +953,31 @@ impl AndroidPlatform {
}
}

/// Android's feel constants for GPUI's portable gesture recognizers.
///
/// Without this GPUI falls back to [`GestureTuning::default`], whose scroll
/// physics are `UIScrollView`'s — an exponential decay that coasts noticeably
/// longer than Android's, so a fling reads as unresponsive to anyone used to
/// the platform.
struct AndroidGestures;

impl PlatformGestures for AndroidGestures {
fn tuning(&self) -> GestureTuning {
GestureTuning {
// AOSP's `OverScroller` friction spline. GPUI flings in logical
// pixels, and this window's scale factor is Android's display
// density, so logical pixels are density-independent pixels and
// the nominal 160 dpi pairing this constructor documents is the
// right one.
scroll_physics: ScrollPhysics::android(),
// The rest of `ViewConfiguration` (touch slop, tap timeouts) is
// close enough to GPUI's defaults to leave alone; reading the real
// values over JNI is a separate change.
..GestureTuning::default()
}
}
}

// ── impl Platform ─────────────────────────────────────────────────────────────
//
// Implementation of the GPUI `Platform` trait for Android.
Expand All @@ -961,6 +987,10 @@ impl AndroidPlatform {
// file pickers, etc.) are no-ops or return sensible defaults.

impl Platform for AndroidPlatform {
fn gestures(&self) -> Option<Rc<dyn PlatformGestures>> {
Some(Rc::new(AndroidGestures))
}

fn background_executor(&self) -> BackgroundExecutor {
let dispatcher: Arc<dyn gpui::PlatformDispatcher> = self.state.lock().dispatcher.clone();
BackgroundExecutor::new(dispatcher)
Expand Down Expand Up @@ -1310,6 +1340,9 @@ impl SharedPlatform {
/// so the compiler never accidentally picks the inherent method (which may
/// have a different return type).
impl Platform for SharedPlatform {
fn gestures(&self) -> Option<Rc<dyn PlatformGestures>> {
<AndroidPlatform as Platform>::gestures(&self.0)
}
fn background_executor(&self) -> BackgroundExecutor {
<AndroidPlatform as Platform>::background_executor(&self.0)
}
Expand Down