// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. use std::cell::{ Cell, RefCell }; use std::collections::HashMap; use std::marker::PhantomData; use std::sync::Arc; use web_sys::{ Element, HtmlElement, OffscreenCanvas, WebGl2RenderingContext }; use crate::app::{ App, SubsurfaceId }; use crate::event_loop::SurfaceFocus; use crate::render::Canvas; use super::desktop::Connection; use super::run::Listener; use super::subsurfaces::WebSubsurface; /// What the runtime's requests go through in a browser: the page's desktop when it has one, the one WebGL context every surface draws through, and what the loop must report back to the runtime. pub( crate ) struct WebPlatform { /// forge/web, when the page has it; the app then has no canvas of its own over the page. pub desktop: Option, /// Where every surface is painted on the GPU before its frame moves to the surface's own canvas, so the app holds one WebGL context however many surfaces it has. `None` without WebGL2: each surface is then drawn in software. pub offscreen: Option, pub webgl: Option, /// `webgl` as the GPU canvas draws through it, wrapped anew when the context is restored. pub gl: Option>, /// The browser took the WebGL context away; nothing is drawn until it gives it back. pub context_lost: bool, /// Where the cursor's shape is set: the element taking the input of the surface the pointer last entered, since CSS gives each element its own. pub cursor_target: HtmlElement, /// The cursor's shape as CSS names it, for the next surface the pointer enters. pub cursor: &'static str, /// A hidden element padded by `env( safe-area-inset-* )`, which tells what a phone's notch and bars cover; only on ltk's own canvas over the page. pub inset_probe: Option, /// The surfaces a frame was presented or asked for; the loop reports them at the next animation frame. pub frame_due: RefCell>, /// The id the next overlay's or subsurface's surface goes by in forge/web's protocol. pub next_surface: Cell, /// The subsurfaces of [`App::subsurfaces`], each a surface of the desktop. pub subsurfaces: HashMap, /// The GPU canvas every subsurface is painted on before its frame moves to its own canvas, which keeps it. pub subsurface_canvas: Option, /// Text input was asked for and not ended; ltk's own keyboard serves it. pub text_input: bool, /// The input of every surface, the window's `resize` and the context's loss, removed when the app exits. pub listeners: Vec, _app: PhantomData A>, } impl WebPlatform { pub( crate ) fn new( desktop: Option, offscreen: Option<( OffscreenCanvas, WebGl2RenderingContext )>, cursor_target: HtmlElement, listeners: Vec, inset_probe: Option ) -> Self { let ( offscreen, webgl ) = offscreen.unzip(); let gl = webgl.clone().map( |context| Arc::new( glow::Context::from_webgl2_context( context ) ) ); Self { desktop, offscreen, webgl, gl, context_lost: false, cursor_target, cursor: "default", inset_probe, frame_due: RefCell::new( Vec::new() ), next_surface: Cell::new( super::desktop::MAIN + 1 ), subsurfaces: HashMap::new(), subsurface_canvas: None, text_input: false, listeners, _app: PhantomData, } } /// Report a frame of `focus` at the next animation frame. pub( super ) fn owe_frame( &self, focus: SurfaceFocus ) { let mut due = self.frame_due.borrow_mut(); if !due.contains( &focus ) { due.push( focus ); } } pub( super ) fn surface_id( &self ) -> u32 { let id = self.next_surface.get(); self.next_surface.set( id + 1 ); id } }