openpencil/crates/op-host-native/src/widget_host/account_press.rs
2026-07-23 21:23:21 +08:00

133 lines
5.7 KiB
Rust

//! Sign-in modal + signed-in account-dropdown press dispatchers.
//!
//! Mirrors the other modal/overlay dispatchers in
//! `property_input_dispatch.rs` (figma-import, export-dialog): each
//! consumes every press while its overlay is open, closing silently on
//! an outside click (still counted as a blank press so chrome text
//! inputs blur).
use super::WidgetHostNative;
use op_editor_core::AccountMenuRow;
use op_editor_ui::widgets::account_menu::AccountMenu;
use op_editor_ui::widgets::login_modal::{LoginModal, LoginModalHit};
use op_editor_ui::widgets::top_bar::TopBar;
use op_editor_ui::widgets::TOP_BAR_HEIGHT;
use op_editor_ui::{Point2D, Rect};
impl WidgetHostNative {
/// Sign-in-modal press dispatcher.
pub(in crate::widget_host) fn dispatch_login_modal_press(
&mut self,
x: f32,
y: f32,
viewport_w: f32,
viewport_h: f32,
) {
let modal = LoginModal::for_editor(&self.editor_state);
let panel_rect = modal.rect(viewport_w, viewport_h);
let hit = modal.hit_test(panel_rect, Point2D::new(x, y));
self.editor_state.editor_ui.pressed_button =
op_editor_ui::widgets::editor_state_ext::login_modal_button(hit)
.map(op_editor_core::ButtonPressTarget::LoginModal);
match hit {
LoginModalHit::Close => {
self.editor_state.editor_ui.login_modal_open = false;
self.editor_state.editor_ui.login_modal_hover = None;
self.editor_state.editor_ui.login_modal_stub_hint_shown = false;
}
LoginModalHit::Outside => {
self.blur_text_inputs_on_blank_press();
self.editor_state.editor_ui.login_modal_open = false;
self.editor_state.editor_ui.login_modal_hover = None;
self.editor_state.editor_ui.login_modal_stub_hint_shown = false;
}
LoginModalHit::SignIn => {
// Dev/demo fast path — never reachable in a production
// build; the planned real flow uses OIDC Auth Code +
// PKCE via the system browser.
if dev_fake_login_enabled() {
self.editor_state.editor_ui.account =
op_editor_core::AccountState::dev_fake_signed_in();
self.editor_state.editor_ui.login_modal_open = false;
self.editor_state.editor_ui.login_modal_hover = None;
self.editor_state.editor_ui.login_modal_stub_hint_shown = false;
} else {
// Honest stub: no session is created — just reveal
// the "coming soon" note instead of pretending the
// OIDC flow ran.
self.editor_state.editor_ui.login_modal_stub_hint_shown = true;
}
}
LoginModalHit::Inside => {
self.blur_text_inputs_on_blank_press();
}
}
self.mark_dirty();
}
/// Signed-in account-dropdown press dispatcher.
pub(in crate::widget_host) fn dispatch_account_menu_press(
&mut self,
x: f32,
y: f32,
viewport_w: f32,
_viewport_h: f32,
) {
let top_bar_rect = Rect {
origin: Point2D::new(0.0, 0.0),
size: Point2D::new(viewport_w, TOP_BAR_HEIGHT),
};
let top_bar = TopBar::for_editor_ui(&self.editor_state.editor_ui);
let anchor = top_bar.account_button_rect(top_bar_rect);
let Some(menu) = AccountMenu::for_editor_ui(&self.editor_state.editor_ui) else {
// Account state flipped back to Anonymous out from under an
// open menu (shouldn't normally happen) — close defensively.
self.editor_state.editor_ui.account_menu_open = false;
return;
};
let menu_rect = menu.rect_at(anchor);
let point = Point2D::new(x, y);
match menu.hit_test(menu_rect, point) {
Some(AccountMenuRow::Workspace) => {
// No submenu exists yet — a literal no-op. The menu
// stays open (unlike Settings / Sign Out, which both
// have a real action to complete).
}
Some(AccountMenuRow::Settings) => {
self.close_account_menu();
self.editor_state.editor_ui.agent_settings_open = true;
self.editor_state.editor_ui.agent_settings.tab =
op_editor_core::agent_settings::AgentSettingsTab::Account;
self.editor_state.chat.blur_input(self.now_ms);
}
Some(AccountMenuRow::SignOut) => {
self.close_account_menu();
self.editor_state.editor_ui.account = op_editor_core::AccountState::Anonymous;
}
None => {
if !(menu_rect).contains(point) {
// Outside click — blank press: dismiss + blur inputs.
self.blur_text_inputs_on_blank_press();
self.close_account_menu();
}
}
}
self.mark_dirty();
}
fn close_account_menu(&mut self) {
self.editor_state.editor_ui.account_menu_open = false;
self.editor_state.editor_ui.account_menu_hover = None;
}
}
/// Dev/demo fast path: `OPENPENCIL_DEV_FAKE_LOGIN=1` makes the sign-in
/// modal's primary button set `AccountState::SignedIn` immediately
/// instead of showing the honest "coming soon" stub. Lets the topbar
/// avatar, its dropdown, and the settings Account tab be exercised
/// end-to-end before the real OIDC backend lands. Never read outside
/// this native host, so it can't leak into the wasm web build.
fn dev_fake_login_enabled() -> bool {
std::env::var("OPENPENCIL_DEV_FAKE_LOGIN").as_deref() == Ok("1")
}