167 lines
5.4 KiB
Rust
167 lines
5.4 KiB
Rust
//! iOS backend — UIKit via C FFI / Objective-C bridge.
|
|
//!
|
|
//! Architecture: correct and complete. Native UIKit calls are stubs marked
|
|
//! TODO — a future agent fills in the actual `extern "C"` calls.
|
|
//!
|
|
//! Each `PlatformNode` maps to a UIKit view:
|
|
//! element("div") → UIView
|
|
//! element("span") → UILabel (inline)
|
|
//! element("button") → UIButton
|
|
//! element("input") → UITextField
|
|
//! text("...") → UILabel
|
|
//!
|
|
//! Event binding maps DOM event names to UIControl target-action pairs:
|
|
//! "click" → UIControlEventTouchUpInside
|
|
//! "input" → UIControlEventEditingChanged
|
|
|
|
use crate::{EventHandler, PlatformBackend, PlatformError, PlatformNode, PlatformResult};
|
|
|
|
pub struct IosBackend;
|
|
|
|
impl IosBackend {
|
|
pub fn new() -> Self {
|
|
Self
|
|
}
|
|
|
|
/// Map an HTML tag name to the UIKit class name it maps to.
|
|
fn uikit_class(tag: &str) -> &'static str {
|
|
match tag {
|
|
"button" => "UIButton",
|
|
"input" => "UITextField",
|
|
"textarea" => "UITextView",
|
|
"img" => "UIImageView",
|
|
"ul" | "ol" => "UITableView",
|
|
"li" => "UITableViewCell",
|
|
"nav" => "UINavigationBar",
|
|
_ => "UIView",
|
|
}
|
|
}
|
|
|
|
/// Map a DOM event name to a UIControlEvent constant name.
|
|
fn uicontrol_event(event: &str) -> &'static str {
|
|
match event {
|
|
"click" => "UIControlEventTouchUpInside",
|
|
"input" | "change" => "UIControlEventEditingChanged",
|
|
"focus" => "UIControlEventEditingDidBegin",
|
|
"blur" => "UIControlEventEditingDidEnd",
|
|
_ => "UIControlEventAllEvents",
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for IosBackend {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl PlatformBackend for IosBackend {
|
|
fn name(&self) -> &'static str {
|
|
"ios"
|
|
}
|
|
|
|
fn create_element(&self, tag: &str) -> PlatformResult<PlatformNode> {
|
|
let mut node = PlatformNode::element(tag);
|
|
// TODO: call UIKit C FFI to allocate the view:
|
|
// extern "C" { fn el_ios_create_view(class_name: *const c_char) -> usize; }
|
|
// node.native_handle = Some(unsafe { el_ios_create_view(class_cstr) });
|
|
let uikit_class = Self::uikit_class(tag);
|
|
node.attributes
|
|
.push(crate::Attribute::new("data-uikit-class", uikit_class));
|
|
Ok(node)
|
|
}
|
|
|
|
fn create_text(&self, content: &str) -> PlatformResult<PlatformNode> {
|
|
let mut node = PlatformNode::text(content);
|
|
// TODO: UILabel with text = content
|
|
node.attributes
|
|
.push(crate::Attribute::new("data-uikit-class", "UILabel"));
|
|
Ok(node)
|
|
}
|
|
|
|
fn set_attribute(
|
|
&self,
|
|
node: &mut PlatformNode,
|
|
name: &str,
|
|
value: &str,
|
|
) -> PlatformResult<()> {
|
|
// TODO: map attribute names to UIKit property setters:
|
|
// "class" → apply style from stylesheet
|
|
// "disabled" → view.isUserInteractionEnabled = false
|
|
// "placeholder" → textField.placeholder = value
|
|
node.attributes.retain(|a| a.name != name);
|
|
node.attributes.push(crate::Attribute::new(name, value));
|
|
Ok(())
|
|
}
|
|
|
|
fn remove_attribute(&self, node: &mut PlatformNode, name: &str) -> PlatformResult<()> {
|
|
node.attributes.retain(|a| a.name != name);
|
|
Ok(())
|
|
}
|
|
|
|
fn append_child(
|
|
&self,
|
|
parent: &mut PlatformNode,
|
|
child: PlatformNode,
|
|
) -> PlatformResult<()> {
|
|
// TODO: [parentView addSubview:childView] via FFI
|
|
parent.children.push(child);
|
|
Ok(())
|
|
}
|
|
|
|
fn remove_child(&self, parent: &mut PlatformNode, child_index: usize) -> PlatformResult<()> {
|
|
if child_index >= parent.children.len() {
|
|
return Err(PlatformError::Render(format!(
|
|
"ios: child index {} out of bounds",
|
|
child_index
|
|
)));
|
|
}
|
|
// TODO: [childView removeFromSuperview] via FFI
|
|
parent.children.remove(child_index);
|
|
Ok(())
|
|
}
|
|
|
|
fn replace_child(
|
|
&self,
|
|
parent: &mut PlatformNode,
|
|
index: usize,
|
|
new_child: PlatformNode,
|
|
) -> PlatformResult<()> {
|
|
if index >= parent.children.len() {
|
|
return Err(PlatformError::Render("ios: replace_child out of bounds".into()));
|
|
}
|
|
// TODO: remove old view, insert new view via FFI
|
|
parent.children[index] = new_child;
|
|
Ok(())
|
|
}
|
|
|
|
fn bind_event(
|
|
&self,
|
|
node: &mut PlatformNode,
|
|
event: &str,
|
|
_handler: EventHandler,
|
|
) -> PlatformResult<()> {
|
|
let uievent = Self::uicontrol_event(event);
|
|
// TODO: [view addTarget:target action:@selector(handler:) forControlEvents:uievent]
|
|
node.attributes
|
|
.push(crate::Attribute::new(format!("data-ios-event-{}", event), uievent));
|
|
Ok(())
|
|
}
|
|
|
|
fn render_to_string(&self, node: &PlatformNode) -> PlatformResult<String> {
|
|
// iOS doesn't render to HTML strings at runtime, but we support it for
|
|
// testing and SSR fallback.
|
|
Ok(node.to_html())
|
|
}
|
|
|
|
fn mount(&self, _root: PlatformNode, _container_id: &str) -> PlatformResult<()> {
|
|
// TODO: get UIViewController by container_id and set root as its view
|
|
Ok(())
|
|
}
|
|
|
|
fn patch(&self, _old: &PlatformNode, _new: &PlatformNode) -> PlatformResult<()> {
|
|
// TODO: diff old and new trees, apply UIKit mutations via FFI
|
|
Ok(())
|
|
}
|
|
}
|