164 lines
5.3 KiB
Rust
164 lines
5.3 KiB
Rust
use std::time::Duration;
|
|
|
|
use ely_gpui_component::Assets;
|
|
use ely_gpui_component::theme::ActiveTheme;
|
|
use ely_platform::{AppId, env, graphics, logging, startup};
|
|
use gpui::{
|
|
App, Bounds, Entity, Render, TitlebarOptions, Window, WindowBounds, WindowDecorations,
|
|
WindowOptions, px, size,
|
|
};
|
|
|
|
use crate::theme;
|
|
|
|
/// Starts `app`; `ELY_SMOKE=1` draws a frame, `hold` stays up.
|
|
pub fn run<V: Render>(
|
|
app: AppId,
|
|
root: impl FnOnce(AppId, &mut Window, &mut App) -> Entity<V> + 'static,
|
|
) {
|
|
if let Err(error) = logging::init(app) {
|
|
startup::fail(app, &format!("logging did not start: {error:#}"));
|
|
}
|
|
if let Err(error) = graphics::check() {
|
|
startup::fail(app, &format!("{error:#}"));
|
|
}
|
|
startup::guard(app);
|
|
startup::no_window_restore();
|
|
let smoke = Smoke::from_env();
|
|
let application = gpui_platform::application().with_assets(Assets);
|
|
let asked = crate::opening::listen(&application);
|
|
application.run(move |cx: &mut App| {
|
|
crate::opening::keep(asked, cx);
|
|
open(app, smoke, root, cx)
|
|
});
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
enum Smoke {
|
|
Off,
|
|
Once,
|
|
Hold,
|
|
}
|
|
|
|
impl Smoke {
|
|
fn from_env() -> Self {
|
|
Self::from_value(env::value("ELY_SMOKE").as_deref())
|
|
}
|
|
|
|
fn from_value(value: Option<&str>) -> Self {
|
|
match value {
|
|
None => Smoke::Off,
|
|
Some("1") => Smoke::Once,
|
|
Some("hold") => Smoke::Hold,
|
|
Some(other) => {
|
|
log::warn!("run: ELY_SMOKE={other} is not 1 or hold, ignored");
|
|
Smoke::Off
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn open<V: Render>(
|
|
app: AppId,
|
|
smoke: Smoke,
|
|
root: impl FnOnce(AppId, &mut Window, &mut App) -> Entity<V> + 'static,
|
|
cx: &mut App,
|
|
) {
|
|
if let Err(error) = ely_gpui_component::init(cx) {
|
|
startup::fail(app, &format!("the components did not start: {error:#}"));
|
|
}
|
|
crate::settings::install(app, cx);
|
|
// Clears asked for last time; nothing opens what stays.
|
|
if let Err(error) = ely_platform::storage::clear_marked(app) {
|
|
startup::fail(app, &format!("the data you asked to clear could not be deleted, so it is not opened; the next start tries again: {error}"));
|
|
}
|
|
theme::install(cx);
|
|
crate::i18n::install(cx);
|
|
crate::commands::bind_keys(cx);
|
|
crate::commands::bind_quit(cx);
|
|
cx.on_action(move |_: &crate::commands::OpenSettings, cx| crate::settings::open(app, cx));
|
|
let mut options = WindowOptions {
|
|
focus: smoke == Smoke::Off,
|
|
inactive_frame_interval: (smoke != Smoke::Hold).then_some(Duration::from_millis(250)),
|
|
..window_options(app, size(px(1100.0), px(720.0)), cx)
|
|
};
|
|
if let Some(bounds) = crate::settings::restored_frame(app, cx) {
|
|
options.window_bounds = Some(bounds);
|
|
}
|
|
let opened = cx.open_window(options, |window, cx| {
|
|
window.on_next_frame(move |_, cx| {
|
|
startup::started();
|
|
if smoke == Smoke::Once {
|
|
println!("ready {}", app.name());
|
|
cx.quit();
|
|
}
|
|
});
|
|
theme::follow_system(window);
|
|
crate::settings::follow_frame(app, window, cx);
|
|
root(app, window, cx)
|
|
});
|
|
if let Err(error) = opened {
|
|
startup::fail(app, &format!("the window did not open: {error:#}"));
|
|
}
|
|
cx.on_window_closed(|cx, _| {
|
|
if cx.windows().is_empty() {
|
|
cx.quit();
|
|
}
|
|
})
|
|
.detach();
|
|
if smoke == Smoke::Off {
|
|
cx.activate(true);
|
|
}
|
|
// Benchmarks only: idle apps do nothing (PERF-02).
|
|
if ely_platform::env::flag("ELY_STALLS") == Some(true) {
|
|
watch_stalls(cx);
|
|
}
|
|
log::info!("{} window open", app.name());
|
|
}
|
|
|
|
/// Logs each main-thread stretch busy past 100 ms.
|
|
fn watch_stalls(cx: &mut App) {
|
|
const BEAT: Duration = Duration::from_millis(20);
|
|
cx.spawn(async move |cx: &mut gpui::AsyncApp| {
|
|
loop {
|
|
let asked = std::time::Instant::now();
|
|
cx.background_executor().timer(BEAT).await;
|
|
let late = asked.elapsed().saturating_sub(BEAT);
|
|
if late > Duration::from_millis(100) {
|
|
log::warn!("ui: the main thread was busy {} ms", late.as_millis());
|
|
}
|
|
}
|
|
})
|
|
.detach();
|
|
}
|
|
|
|
/// A window of `app`, `size` large, with Ely's title bar.
|
|
pub(crate) fn window_options(app: AppId, size: gpui::Size<gpui::Pixels>, cx: &App) -> WindowOptions {
|
|
WindowOptions {
|
|
window_bounds: Some(WindowBounds::Windowed(Bounds::centered(None, size, cx))),
|
|
titlebar: Some(TitlebarOptions {
|
|
title: Some(app.name().into()),
|
|
appears_transparent: true,
|
|
traffic_light_position: Some(cx.theme().traffic_light_origin()),
|
|
}),
|
|
window_decorations: Some(WindowDecorations::Client),
|
|
window_min_size: Some(gpui::size(px(640.0), px(420.0))),
|
|
app_id: Some(app.slug().into()),
|
|
inactive_frame_interval: Some(Duration::from_millis(250)),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::Smoke;
|
|
|
|
#[test]
|
|
fn only_one_and_hold_start_a_smoke_run() {
|
|
assert!(Smoke::from_value(Some("1")) == Smoke::Once);
|
|
assert!(Smoke::from_value(Some("hold")) == Smoke::Hold);
|
|
for other in [None, Some("0"), Some(""), Some("true")] {
|
|
assert!(Smoke::from_value(other) == Smoke::Off, "{other:?}");
|
|
}
|
|
}
|
|
}
|