use day_pieces::combobox::*;
use day_spec::Event;
use std::cell::RefCell;
use std::collections::HashMap;
use crate::AppKit;
use day_spec::{NodeId, Proposal, Size};
use objc2::rc::Retained;
use objc2::runtime::{NSObjectProtocol, ProtocolObject};
use objc2::{DefinedClass, MainThreadMarker, MainThreadOnly, define_class, msg_send};
use objc2_app_kit::{
NSComboBox, NSComboBoxDelegate, NSControlTextEditingDelegate, NSTextField, NSTextFieldDelegate,
NSView,
};
use objc2_foundation::{NSNotification, NSObject, NSString};
struct ComboIvars {
node: NodeId,
}
define_class!(
#[unsafe(super(NSObject))]
#[thread_kind = MainThreadOnly]
#[name = "DayComboTarget"]
#[ivars = ComboIvars]
struct ComboTarget;
unsafe impl NSObjectProtocol for ComboTarget {}
unsafe impl NSTextFieldDelegate for ComboTarget {}
unsafe impl NSControlTextEditingDelegate for ComboTarget {
#[unsafe(method(controlTextDidChange:))]
fn control_text_did_change(&self, notification: &NSNotification) {
let node = self.ivars().node;
if let Some(obj) = notification.object()
&& let Ok(tf) = obj.downcast::<NSTextField>()
{
crate::emit(node, Event::TextChanged(tf.stringValue().to_string()));
}
}
}
unsafe impl NSComboBoxDelegate for ComboTarget {
#[unsafe(method(comboBoxSelectionDidChange:))]
fn combo_box_selection_did_change(&self, notification: &NSNotification) {
let node = self.ivars().node;
if let Some(obj) = notification.object()
&& let Ok(combo) = obj.downcast::<NSComboBox>()
{
let idx = combo.indexOfSelectedItem();
if idx >= 0
&& let Ok(s) = combo.itemObjectValueAtIndex(idx).downcast::<NSString>()
{
crate::emit(node, Event::TextChanged(s.to_string()));
}
}
}
}
);
impl ComboTarget {
fn new(mtm: MainThreadMarker, node: NodeId) -> Retained<Self> {
let this = Self::alloc(mtm).set_ivars(ComboIvars { node });
unsafe { msg_send![super(this), init] }
}
}
day_core::tls_group! {
static TARGETS: RefCell<HashMap<usize, Retained<ComboTarget>>> = RefCell::new(HashMap::new());
}
fn apply_items(combo: &NSComboBox, items: &[String]) {
combo.removeAllItems();
for item in items {
unsafe { combo.addItemWithObjectValue(&NSString::from_str(item)) };
}
}
fn make(backend: &mut AppKit, p: &ComboProps, id: NodeId) -> Retained<NSView> {
let mtm = backend.mtm();
let combo = NSComboBox::new(mtm);
apply_items(&combo, &p.items);
combo.setCompletes(true);
if !p.placeholder.is_empty() {
let tf: &NSTextField = combo.as_ref();
tf.setPlaceholderString(Some(&NSString::from_str(&p.placeholder)));
}
combo.setStringValue(&NSString::from_str(&p.text));
let target = ComboTarget::new(mtm, id);
unsafe { combo.setDelegate(Some(ProtocolObject::from_ref(&*target))) };
let ns: Retained<NSView> = Retained::from(<NSComboBox as AsRef<NSView>>::as_ref(&combo));
TARGETS.with(|m| {
m.borrow_mut()
.insert((ns.as_ref() as *const NSView) as usize, target)
});
ns
}
fn update(_backend: &mut AppKit, h: &Retained<NSView>, patch: &ComboPatch) {
let Some(combo) = h.downcast_ref::<NSComboBox>() else {
return;
};
match patch {
ComboPatch::Items(items) => apply_items(combo, items),
ComboPatch::SetText(t) => {
if combo.stringValue().to_string() != *t {
combo.setStringValue(&NSString::from_str(t));
}
}
}
}
fn measure(_backend: &mut AppKit, h: &Retained<NSView>, p: Proposal) -> Size {
let fit = h.fittingSize();
let w = p.width.unwrap_or(fit.width).max(120.0);
Size::new(w, fit.height.ceil().max(22.0))
}
fn release(_backend: &mut AppKit, h: &Retained<NSView>) {
TARGETS.with(|m| {
m.borrow_mut()
.remove(&((h.as_ref() as *const NSView) as usize));
});
}
day_pieces::renderer!(crate::RENDERERS, AppKit,
kind: KIND, props: ComboProps, patch: ComboPatch,
make: make, update: update, measure: measure, release: release);