use day_pieces::searchfield::*;
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::{
NSControlTextEditingDelegate, NSSearchField, NSTextField, NSTextFieldDelegate, NSView,
};
use objc2_foundation::{NSNotification, NSObject, NSString};
struct SearchIvars {
node: NodeId,
}
define_class!(
#[unsafe(super(NSObject))]
#[thread_kind = MainThreadOnly]
#[name = "DaySearchFieldTarget"]
#[ivars = SearchIvars]
struct SearchTarget;
unsafe impl NSObjectProtocol for SearchTarget {}
unsafe impl NSTextFieldDelegate for SearchTarget {}
unsafe impl NSControlTextEditingDelegate for SearchTarget {
#[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()));
}
}
}
);
impl SearchTarget {
fn new(mtm: MainThreadMarker, node: NodeId) -> Retained<Self> {
let this = Self::alloc(mtm).set_ivars(SearchIvars { node });
unsafe { msg_send![super(this), init] }
}
}
day_core::tls_group! {
static TARGETS: RefCell<HashMap<usize, Retained<SearchTarget>>> = RefCell::new(HashMap::new());
}
fn make(backend: &mut AppKit, p: &SearchProps, id: NodeId) -> Retained<NSView> {
let mtm = backend.mtm();
let field = NSSearchField::new(mtm);
if !p.placeholder.is_empty() {
field.setPlaceholderString(Some(&NSString::from_str(&p.placeholder)));
}
field.setStringValue(&NSString::from_str(&p.text));
let target = SearchTarget::new(mtm, id);
let tf: &NSTextField = field.as_ref();
unsafe { tf.setDelegate(Some(ProtocolObject::from_ref(&*target))) };
let ns: Retained<NSView> = Retained::from(<NSSearchField as AsRef<NSView>>::as_ref(&field));
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: &SearchPatch) {
let SearchPatch::SetText(t) = patch;
if let Some(field) = h.downcast_ref::<NSSearchField>()
&& field.stringValue().to_string() != *t
{
field.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: SearchProps, patch: SearchPatch,
make: make, update: update, measure: measure, release: release);