use std::collections::HashMap;
use core_graphics::base::CGFloat;
use objc::runtime::{Class, Object};
use objc::{class, msg_send, sel, sel_impl};
use objc_id::ShareId;
use crate::color::Color;
use crate::foundation::{id, nil, NSArray, NSInteger, NSString, NSUInteger, NO, YES};
use crate::layout::Layout;
#[cfg(feature = "autolayout")]
use crate::layout::{LayoutAnchorDimension, LayoutAnchorX, LayoutAnchorY};
use crate::objc_access::ObjcAccess;
use crate::scrollview::ScrollView;
use crate::utils::properties::{ObjcProperty, PropertyNullable};
use crate::utils::{os, CGSize, CellFactory};
use crate::view::{ViewAnimatorProxy, ViewDelegate};
#[cfg(feature = "appkit")]
use crate::appkit::menu::MenuItem;
#[cfg(feature = "appkit")]
mod appkit;
#[cfg(feature = "appkit")]
use appkit::{register_listview_class, register_listview_class_with_delegate};
mod enums;
pub use enums::{RowAnimation, RowEdge};
mod traits;
pub use traits::ListViewDelegate;
mod row;
pub use row::ListViewRow;
mod actions;
pub use actions::{RowAction, RowActionStyle};
pub(crate) static LISTVIEW_DELEGATE_PTR: &str = "rstListViewDelegatePtr";
use std::any::Any;
use std::sync::{Arc, RwLock};
use std::cell::RefCell;
use std::rc::Rc;
fn common_init(class: *const Class) -> id {
unsafe {
let tableview: id = msg_send![class, new];
#[cfg(feature = "appkit")]
{
let menu: id = msg_send![class!(NSMenu), new];
let _: () = msg_send![menu, setDelegate: tableview];
let _: () = msg_send![tableview, setMenu: menu];
let _: () = msg_send![tableview, setWantsLayer: YES];
let _: () = msg_send![tableview, setUsesAutomaticRowHeights: YES];
let _: () = msg_send![tableview, setFloatsGroupRows: YES];
let _: () = msg_send![tableview, setColumnAutoresizingStyle:1];
let _: () = msg_send![tableview, setHeaderView: nil];
let identifier = NSString::no_copy("CacaoListViewColumn");
let default_column_alloc: id = msg_send![class!(NSTableColumn), new];
let default_column: id = msg_send![default_column_alloc, initWithIdentifier:&*identifier];
let _: () = msg_send![default_column, setResizingMask:(1<<0)];
let _: () = msg_send![tableview, addTableColumn: default_column];
}
tableview
}
}
#[derive(Debug)]
pub struct ListView<T = ()> {
cell_factory: CellFactory,
menu: PropertyNullable<Vec<MenuItem>>,
pub objc: ObjcProperty,
pub animator: ViewAnimatorProxy,
pub scrollview: ScrollView,
pub delegate: Option<Box<T>>,
#[cfg(feature = "autolayout")]
pub top: LayoutAnchorY,
#[cfg(feature = "autolayout")]
pub leading: LayoutAnchorX,
#[cfg(feature = "autolayout")]
pub left: LayoutAnchorX,
#[cfg(feature = "autolayout")]
pub trailing: LayoutAnchorX,
#[cfg(feature = "autolayout")]
pub right: LayoutAnchorX,
#[cfg(feature = "autolayout")]
pub bottom: LayoutAnchorY,
#[cfg(feature = "autolayout")]
pub width: LayoutAnchorDimension,
#[cfg(feature = "autolayout")]
pub height: LayoutAnchorDimension,
#[cfg(feature = "autolayout")]
pub center_x: LayoutAnchorX,
#[cfg(feature = "autolayout")]
pub center_y: LayoutAnchorY
}
impl Default for ListView {
fn default() -> Self {
ListView::new()
}
}
impl ListView {
pub fn new() -> Self {
let class = register_listview_class();
let view = common_init(class);
#[cfg(feature = "appkit")]
let scrollview = {
let sview = ScrollView::new();
sview.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setDocumentView: view];
});
sview
};
#[cfg(all(feature = "appkit", feature = "autolayout"))]
let anchor_view: id = scrollview.objc.get(|obj| unsafe { msg_send![obj, self] });
ListView {
cell_factory: CellFactory::new(),
menu: PropertyNullable::default(),
delegate: None,
#[cfg(feature = "autolayout")]
top: LayoutAnchorY::top(anchor_view),
#[cfg(feature = "autolayout")]
left: LayoutAnchorX::left(anchor_view),
#[cfg(feature = "autolayout")]
leading: LayoutAnchorX::leading(anchor_view),
#[cfg(feature = "autolayout")]
right: LayoutAnchorX::right(anchor_view),
#[cfg(feature = "autolayout")]
trailing: LayoutAnchorX::trailing(anchor_view),
#[cfg(feature = "autolayout")]
bottom: LayoutAnchorY::bottom(anchor_view),
#[cfg(feature = "autolayout")]
width: LayoutAnchorDimension::width(anchor_view),
#[cfg(feature = "autolayout")]
height: LayoutAnchorDimension::height(anchor_view),
#[cfg(feature = "autolayout")]
center_x: LayoutAnchorX::center(anchor_view),
#[cfg(feature = "autolayout")]
center_y: LayoutAnchorY::center(anchor_view),
animator: ViewAnimatorProxy::new(anchor_view),
objc: ObjcProperty::retain(view),
scrollview
}
}
}
impl<T> ListView<T>
where
T: ListViewDelegate + 'static
{
pub fn with(delegate: T) -> ListView<T> {
let class = register_listview_class_with_delegate::<T>(&delegate);
let view = common_init(class);
let mut delegate = Box::new(delegate);
let cell = CellFactory::new();
unsafe {
let delegate_ptr: *const T = &*delegate;
(&mut *view).set_ivar(LISTVIEW_DELEGATE_PTR, delegate_ptr as usize);
let _: () = msg_send![view, setDelegate: view];
let _: () = msg_send![view, setDataSource: view];
};
#[cfg(feature = "appkit")]
let scrollview = {
let sview = ScrollView::new();
sview.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setDocumentView: view];
});
sview
};
#[cfg(all(feature = "appkit", feature = "autolayout"))]
let anchor_view: id = scrollview.objc.get(|obj| unsafe { msg_send![obj, self] });
let mut view = ListView {
cell_factory: cell,
menu: PropertyNullable::default(),
delegate: None,
objc: ObjcProperty::retain(view),
animator: ViewAnimatorProxy::new(anchor_view),
#[cfg(feature = "autolayout")]
top: LayoutAnchorY::top(anchor_view),
#[cfg(feature = "autolayout")]
left: LayoutAnchorX::left(anchor_view),
#[cfg(feature = "autolayout")]
leading: LayoutAnchorX::leading(anchor_view),
#[cfg(feature = "autolayout")]
right: LayoutAnchorX::right(anchor_view),
#[cfg(feature = "autolayout")]
trailing: LayoutAnchorX::trailing(anchor_view),
#[cfg(feature = "autolayout")]
bottom: LayoutAnchorY::bottom(anchor_view),
#[cfg(feature = "autolayout")]
width: LayoutAnchorDimension::width(anchor_view),
#[cfg(feature = "autolayout")]
height: LayoutAnchorDimension::height(anchor_view),
#[cfg(feature = "autolayout")]
center_x: LayoutAnchorX::center(anchor_view),
#[cfg(feature = "autolayout")]
center_y: LayoutAnchorY::center(anchor_view),
scrollview
};
(&mut delegate).did_load(view.clone_as_handle());
view.delegate = Some(delegate);
view
}
}
impl<T> ListView<T> {
pub fn clone_as_handle(&self) -> ListView {
ListView {
cell_factory: CellFactory::new(),
menu: self.menu.clone(),
delegate: None,
objc: self.objc.clone(),
animator: self.animator.clone(),
#[cfg(feature = "autolayout")]
top: self.top.clone(),
#[cfg(feature = "autolayout")]
leading: self.leading.clone(),
#[cfg(feature = "autolayout")]
left: self.left.clone(),
#[cfg(feature = "autolayout")]
trailing: self.trailing.clone(),
#[cfg(feature = "autolayout")]
right: self.right.clone(),
#[cfg(feature = "autolayout")]
bottom: self.bottom.clone(),
#[cfg(feature = "autolayout")]
width: self.width.clone(),
#[cfg(feature = "autolayout")]
height: self.height.clone(),
#[cfg(feature = "autolayout")]
center_x: self.center_x.clone(),
#[cfg(feature = "autolayout")]
center_y: self.center_y.clone(),
scrollview: self.scrollview.clone_as_handle()
}
}
pub fn register<F, R>(&self, identifier: &'static str, vendor: F)
where
F: Fn() -> R + 'static,
R: ViewDelegate + 'static
{
self.cell_factory.insert(identifier, vendor);
}
pub fn dequeue<R: ViewDelegate + 'static>(&self, identifier: &'static str) -> ListViewRow<R> {
#[cfg(feature = "appkit")]
{
let key = NSString::new(identifier);
let cell: id = self
.objc
.get(|obj| unsafe { msg_send![obj, makeViewWithIdentifier:&*key owner:nil] });
if cell != nil {
ListViewRow::from_cached(cell)
} else {
let delegate: Box<R> = self.cell_factory.get(identifier);
let view = ListViewRow::with_boxed(delegate);
view.set_identifier(identifier);
view
}
}
}
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
self.objc.with_mut(|obj| unsafe {
let color = color.as_ref().cg_color();
let layer: id = msg_send![obj, layer];
let _: () = msg_send![layer, setBackgroundColor: color];
});
}
#[cfg(feature = "appkit")]
pub fn set_style(&self, style: crate::foundation::NSInteger) {
#[cfg(target_os = "macos")]
if os::is_minimum_version(11) {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setStyle: style];
});
}
}
#[cfg(feature = "appkit")]
pub fn set_allows_empty_selection(&self, allows: bool) {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setAllowsEmptySelection:match allows {
true => YES,
false => NO
}];
});
}
pub fn set_selection_highlight_style(&self, style: crate::foundation::NSInteger) {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setSelectionHighlightStyle: style];
});
}
pub fn select_row_indexes(&self, indexes: &[usize], extends_existing: bool) {
unsafe {
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
for index in indexes {
let _: () = msg_send![index_set, addIndex: index];
}
self.objc.with_mut(|obj| {
let _: () = msg_send![obj, selectRowIndexes:index_set byExtendingSelection:match extends_existing {
true => YES,
false => NO
}];
});
}
}
fn hack_avoid_dequeue_loop<F: Fn(&Object)>(&self, handler: F) {
self.objc.get(handler);
}
pub fn perform_batch_updates<F: Fn(ListView)>(&self, update: F) {
#[cfg(feature = "appkit")]
{
self.objc.get(|obj| unsafe {
let _: () = msg_send![obj, beginUpdates];
});
let handle = self.clone_as_handle();
update(handle);
self.hack_avoid_dequeue_loop(|obj| unsafe {
let _: () = msg_send![obj, endUpdates];
});
}
}
pub fn insert_rows(&self, indexes: &[usize], animation: RowAnimation) {
#[cfg(feature = "appkit")]
unsafe {
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
for index in indexes {
let x: NSUInteger = *index as NSUInteger;
let _: () = msg_send![index_set, addIndex: x];
}
let animation_options: NSUInteger = animation.into();
let x = ShareId::from_ptr(index_set);
self.hack_avoid_dequeue_loop(|obj| {
let _: () = msg_send![obj, insertRowsAtIndexes:&*x withAnimation:animation_options];
});
}
}
pub fn reload_rows(&self, indexes: &[usize]) {
#[cfg(feature = "appkit")]
unsafe {
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
for index in indexes {
let x: NSUInteger = *index as NSUInteger;
let _: () = msg_send![index_set, addIndex: x];
}
let x = ShareId::from_ptr(index_set);
let ye: id = msg_send![class!(NSIndexSet), indexSetWithIndex:0];
let y = ShareId::from_ptr(ye);
self.objc.get(|obj| {
let _: () = msg_send![obj, reloadDataForRowIndexes:&*x columnIndexes:&*y];
});
}
}
pub fn remove_rows(&self, indexes: &[usize], animations: RowAnimation) {
#[cfg(feature = "appkit")]
unsafe {
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
for index in indexes {
let x: NSUInteger = *index as NSUInteger;
let _: () = msg_send![index_set, addIndex: x];
}
let animation_options: NSUInteger = animations.into();
let x = ShareId::from_ptr(index_set);
self.objc.with_mut(|obj| {
let _: () = msg_send![obj, removeRowsAtIndexes:&*x withAnimation:animation_options];
});
}
}
pub fn set_row_height(&self, height: CGFloat) {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setRowHeight: height];
});
}
pub fn set_uses_automatic_row_heights(&self, uses: bool) {
#[cfg(feature = "appkit")]
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setUsesAutomaticRowHeights:match uses {
true => YES,
false => NO
}];
});
}
pub fn set_uses_alternating_backgrounds(&self, uses: bool) {
#[cfg(feature = "appkit")]
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setUsesAlternatingRowBackgroundColors:match uses {
true => YES,
false => NO
}];
});
}
pub fn set_row_actions_visible(&self, visible: bool) {
#[cfg(feature = "appkit")]
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setRowActionsVisible:match visible {
true => YES,
false => NO
}];
});
}
#[cfg(feature = "appkit")]
pub fn make_first_responder(&self) {
self.objc.with_mut(|obj| unsafe {
let window: id = msg_send![&*obj, window];
let _: () = msg_send![window, makeFirstResponder:&*obj];
});
}
pub fn reload(&self) {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, reloadData];
});
}
pub fn get_selected_row_index(&self) -> NSInteger {
self.objc.get(|obj| unsafe { msg_send![obj, selectedRow] })
}
pub fn get_clicked_row_index(&self) -> NSInteger {
self.objc.get(|obj| unsafe { msg_send![obj, clickedRow] })
}
}
impl<T> ObjcAccess for ListView<T> {
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
#[cfg(feature = "appkit")]
self.scrollview.objc.with_mut(handler);
}
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
#[cfg(feature = "appkit")]
self.scrollview.objc.get(handler)
}
}
impl<T> Layout for ListView<T> {}
impl<T> Drop for ListView<T> {
fn drop(&mut self) {
}
}