use crate::ferro_access_helper::{FerroAccessHelper, JavaAccessibilityHost, TopLevelAccessView};
use crate::interop::java::{call_int, call_long, call_void, new_object, JavaClass, JavaObject, JavaValue};
use crate::interop::natives::{next_handle, FERRO_ACCESS_HELPER, FERRO_VIEW};
use crate::platform::skia_platform::TopLevelImpl;
use ferroui_base::{BoxedValue, Ref};
use ferroui_controls::automation::peers::ControlAutomationPeer;
use ferroui_controls::embedding::EmbeddableControlRoot;
use ferroui_controls::Control;
use ferroui_controls::platform::ITopLevelImpl;
use ferroui_controls::TopLevel;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::rc::{Rc, Weak};
ferroui_base::ferro_thread_local! {
static VIEWS: RefCell<HashMap<i64, Rc<FerroView>>> = RefCell::new(HashMap::new());
}
const VISIBLE: i32 = 0;
pub struct FerroView {
handle: i64,
java: JavaObject,
root: RefCell<Option<Ref<EmbeddableControlRoot>>>,
view: Rc<TopLevelImpl>,
access_helper: Rc<FerroAccessHelper>,
is_rendering: Cell<bool>,
surface_created: Cell<bool>,
}
impl FerroView {
pub fn new(context: &JavaObject) -> Rc<FerroView> {
let java = new_object(
&JavaClass::find(FERRO_VIEW),
"(Landroid/content/Context;)V",
&[JavaValue::Object(Some(context))],
);
let handle = call_long(&java, "getNativeHandle", "()J", &[]);
match Self::from_handle(handle) {
Some(view) => view,
None => panic!("Unknown error: the initialization of the view has failed."),
}
}
pub(crate) fn from_handle(handle: i64) -> Option<Rc<FerroView>> {
VIEWS.with(|views| views.borrow().get(&handle).cloned())
}
pub(crate) fn native_create(java: JavaObject, context: JavaObject) -> i64 {
let view = TopLevelImpl::new(&java, &context, false);
view.set_focus_change(Some(Rc::new({
let view = Rc::downgrade(&view);
move |has_focus: bool| {
if !has_focus {
let lost_focus = view.upgrade().and_then(|view| view.lost_focus());
if let Some(lost_focus) = lost_focus {
lost_focus();
}
}
}
})));
if let Some(surface_view) = view.view() {
call_void(&java, "addView", "(Landroid/view/View;)V", &[JavaValue::Object(Some(&surface_view))]);
}
let top_level_impl: Rc<dyn ITopLevelImpl> = view.clone();
let root = EmbeddableControlRoot::with_impl(top_level_impl);
root.prepare();
let handle = next_handle();
let java_access_helper = new_object(
&JavaClass::find(FERRO_ACCESS_HELPER),
"(Lorg/ferroui/android/FerroView;J)V",
&[JavaValue::Object(Some(&java)), JavaValue::Long(handle)],
)
.to_global();
let access_helper = FerroAccessHelper::new(
Rc::new(TopLevelAccessView::new(Rc::downgrade(&view))),
Rc::new(JavaAccessibilityHost::new(java_access_helper.clone())),
ControlAutomationPeer::create_peer_for_element(&root),
);
call_void(
&java,
"setAccessibilityDelegate",
"(Landroid/view/View$AccessibilityDelegate;)V",
&[JavaValue::Object(Some(&java_access_helper))],
);
let this = Rc::new(FerroView {
handle,
java,
root: RefCell::new(Some(root)),
view,
access_helper,
is_rendering: Cell::new(false),
surface_created: Cell::new(false),
});
if let Some(internal_view) = this.view.internal_view() {
let weak: Weak<FerroView> = Rc::downgrade(&this);
internal_view.surface_window_created(Rc::new({
let weak = weak.clone();
move || {
if let Some(this) = weak.upgrade() {
this.internal_view_surface_window_created();
}
}
}));
internal_view.surface_window_destroyed(Rc::new(move || {
if let Some(this) = weak.upgrade() {
this.internal_view_surface_window_destroyed();
}
}));
}
VIEWS.with(|views| views.borrow_mut().insert(handle, this));
handle
}
fn internal_view_surface_window_created(&self) {
self.surface_created.set(true);
if call_int(&self.java, "getVisibility", "()I", &[]) == VISIBLE {
self.on_visibility_changed(true);
let root = self.root.borrow().clone();
if let Some(root) = root {
root.invalidate_measure();
}
call_void(&self.java, "invalidate", "()V", &[]);
}
}
fn internal_view_surface_window_destroyed(&self) {
self.on_visibility_changed(false);
self.surface_created.set(false);
}
pub fn java_object(&self) -> &JavaObject {
&self.java
}
pub(crate) fn top_level_impl(&self) -> &Rc<TopLevelImpl> {
&self.view
}
pub(crate) fn access_helper(&self) -> &Rc<FerroAccessHelper> {
&self.access_helper
}
pub fn accessibility_virtual_view_id(&self, control: &Control) -> i32 {
self.access_helper
.get_or_create_node_info_providers_from_peer(&ControlAutomationPeer::create_peer_for_element(control))
}
pub fn top_level(&self) -> Option<Ref<TopLevel>> {
self.root.borrow().clone().map(|root| root.upcast())
}
pub fn content(&self) -> Option<BoxedValue> {
self.root.borrow().as_ref().and_then(|root| root.content())
}
pub fn set_content(&self, value: Option<BoxedValue>) {
let root = self.root.borrow().clone();
if let Some(root) = root {
root.set_content(value);
}
}
pub fn dispose(&self) {
self.on_visibility_changed(false);
self.surface_created.set(false);
let root = self.root.borrow_mut().take();
if let Some(root) = root {
root.dispose();
}
let removed = VIEWS.with(|views| views.borrow_mut().remove(&self.handle));
drop(removed);
}
pub(crate) fn on_visibility_changed(&self, is_visible: bool) {
let root = self.root.borrow().clone();
let Some(root) = root else {
return;
};
if !self.surface_created.get() {
return;
}
if is_visible && !self.is_rendering.get() {
self.is_rendering.set(true);
root.start_rendering();
if let Some(insets_manager) = self.view.insets_manager() {
insets_manager.apply_status_bar_state();
}
} else if !is_visible && self.is_rendering.get() {
self.is_rendering.set(false);
root.stop_rendering();
}
}
pub(crate) fn send_configuration_changed(&self, has_configuration: bool, night: bool) {
if let Some(insets_manager) = self.view.insets_manager() {
insets_manager.set_default_system_light_mode(!(has_configuration && night));
}
if has_configuration {
self.view.screens().on_changed();
}
}
pub(crate) fn on_global_layout(&self) {
self.view.resize(ITopLevelImpl::client_size(&*self.view));
}
}