69 requires ::aria::ListSourceOf<L, T>
73 : state_(
std::make_shared<State>()) {
74 if (![NSThread isMainThread])
throw std::logic_error(
"table bridge construction requires the main thread");
75 state_->events = std::make_shared<EventQueue>();
76 state_->events->target = state_;
77 state_->table = tableView;
78 state_->view_for_row = std::move(view_for_row);
79 state_->snapshot = source.snapshot();
82 std::weak_ptr<State> weak_state = state_;
84 auto row_count_fn = [
weak_state]() -> NSInteger {
85 if (
auto s = weak_state.lock(); s && !s->detached.load(std::memory_order_acquire)) {
86 return static_cast<NSInteger
>(s->snapshot.size());
90 auto view_for_fn = [
weak_state](NSTableView* tv,
92 NSInteger row) -> NSView* {
93 auto s = weak_state.lock();
94 if (!s || s->detached.load(std::memory_order_acquire))
return nil;
96 ||
static_cast<std::size_t
>(row) >= s->snapshot.size()) {
99 auto item = s->snapshot[
static_cast<std::size_t>(row)];
100 if (!item)
return nil;
101 return s->view_for_row(tv, col, item, row);
104 state_->ds = [[AriaTableDataSource alloc]
105 initWithRowCount:std::move(row_count_fn)
106 viewForFn:std::move(view_for_fn)];
107 sub_ = source.observe([events = state_->events](const ::aria::ListChange<T>& change) {
108 enqueue_(events, change);
110 tableView.dataSource = state_->ds;
111 tableView.delegate = state_->ds;
116 state_->detached.store(
true, std::memory_order_release);
118 std::lock_guard lock(state_->events->mutex);
119 state_->events->stopped =
true;
122 if ([NSThread isMainThread]) {
127 auto* owner =
new std::shared_ptr<State>(std::move(state_));
128 dispatch_async_f(dispatch_get_main_queue(), owner, [](
void* context) {
129 std::unique_ptr<std::shared_ptr<State>> state{
static_cast<std::shared_ptr<State>*
>(context)};
140 return state_ ? state_->snapshot.size() : 0;
145 if (!state_)
return nullptr;
146 if (i >= state_->snapshot.size())
return nullptr;
147 return state_->snapshot[
i];
154 std::deque<::aria::ListChange<T>> changes;
155 std::weak_ptr<State> target;
156 bool scheduled =
false;
157 bool stopped =
false;
160 NSTableView* __weak table = nil;
161 AriaTableDataSource* __strong ds = nil;
163 std::vector<std::shared_ptr<T>> snapshot;
164 std::atomic<bool> detached{
false};
165 std::shared_ptr<EventQueue> events;
168 static void cleanup_(State& state) {
169 std::deque<::aria::ListChange<T>> discarded;
171 std::lock_guard lock(state.events->mutex);
172 discarded.swap(state.events->changes);
174 NSTableView* table = state.table;
175 if (table.dataSource == state.ds) table.dataSource = nil;
176 if (table.delegate == state.ds) table.delegate = nil;
179 static void enqueue_(
const std::shared_ptr<EventQueue>& events, const ::aria::ListChange<T>& change) {
181 std::lock_guard lock(events->mutex);
182 if (events->stopped)
return;
183 events->changes.push_back(change);
184 if (events->scheduled)
return;
185 events->scheduled =
true;
187 if ([NSThread isMainThread]) drain_(events);
189 auto pending = events;
190 dispatch_async(dispatch_get_main_queue(), ^{ drain_(pending); });
194 static void drain_(
const std::shared_ptr<EventQueue>& events) {
195 auto state = events->target.lock();
198 ::aria::ListChange<T> change{};
200 std::lock_guard lock(events->mutex);
201 if (events->stopped || events->changes.empty()) {
202 events->scheduled =
false;
205 change = std::move(events->changes.front());
206 events->changes.pop_front();
208 if (state->detached.load(std::memory_order_acquire))
return;
209 try { apply_change_(*state, change); }
214 static void apply_change_(State& s,
215 const ::aria::ListChange<T>& ch) {
218 case K::Insert: apply_insert_(s, ch.index, ch.item);
return;
219 case K::Remove: apply_remove_(s, ch.index);
return;
220 case K::Replace: apply_replace_(s, ch.index, ch.item);
return;
221 case K::ItemChanged: apply_item_changed_(s, ch.index);
return;
222 case K::Move: apply_move_(s, ch.from_index, ch.index);
return;
223 case K::Reset: apply_reset_(s, ch);
return;
227 static void apply_insert_(State& s,
229 const std::shared_ptr<T>& item) {
230 if (idx > s.snapshot.size()) idx = s.snapshot.size();
231 s.snapshot.insert(s.snapshot.begin() +
static_cast<std::ptrdiff_t
>(idx),
233 if (!s.table)
return;
234 NSIndexSet* set = [NSIndexSet indexSetWithIndex:idx];
235 [s.table insertRowsAtIndexes:set
236 withAnimation:NSTableViewAnimationEffectFade];
239 static void apply_remove_(State& s, std::size_t idx) {
240 if (idx >= s.snapshot.size())
return;
241 s.snapshot.erase(s.snapshot.begin() +
static_cast<std::ptrdiff_t
>(idx));
242 if (!s.table)
return;
243 NSIndexSet* set = [NSIndexSet indexSetWithIndex:idx];
244 [s.table removeRowsAtIndexes:set
245 withAnimation:NSTableViewAnimationEffectFade];
248 static void apply_replace_(State& s,
250 const std::shared_ptr<T>& item) {
251 if (idx >= s.snapshot.size())
return;
252 s.snapshot[idx] = item;
253 if (!s.table)
return;
254 NSIndexSet* rowSet = [NSIndexSet indexSetWithIndex:idx];
255 NSIndexSet* colSet = [NSIndexSet
256 indexSetWithIndexesInRange:NSMakeRange(0, s.table.numberOfColumns)];
257 [s.table reloadDataForRowIndexes:rowSet columnIndexes:colSet];
260 static void apply_item_changed_(State& s, std::size_t idx) {
261 if (idx >= s.snapshot.size())
return;
262 if (!s.table)
return;
263 NSIndexSet* rowSet = [NSIndexSet indexSetWithIndex:idx];
264 NSIndexSet* colSet = [NSIndexSet
265 indexSetWithIndexesInRange:NSMakeRange(0, s.table.numberOfColumns)];
266 [s.table reloadDataForRowIndexes:rowSet columnIndexes:colSet];
269 static void apply_move_(State& s, std::size_t from, std::size_t to) {
270 if (from == to)
return;
271 if (from >= s.snapshot.size() || to >= s.snapshot.size())
return;
272 auto moved = s.snapshot[from];
273 s.snapshot.erase(s.snapshot.begin() +
static_cast<std::ptrdiff_t
>(from));
274 s.snapshot.insert(s.snapshot.begin() +
static_cast<std::ptrdiff_t
>(to),
276 if (!s.table)
return;
277 [s.table moveRowAtIndex:static_cast<NSInteger>(from)
278 toIndex:static_cast<NSInteger>(to)];
281 static void apply_reset_(State& s, const ::aria::ListChange<T>& change) {
282 if (!change.snapshot)
throw std::logic_error(
"Reset requires an owned snapshot");
283 s.snapshot = *change.snapshot;
284 if (s.table) [s.table reloadData];
287 std::shared_ptr<State> state_;
288 ::aria::Subscription sub_;