/* * CPSplitViewController.j * * Created by Daniel Boehringer on September 2, 2025. * Copyright (c) 2025 Daniel Boehringer. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @import "CPViewController.j" @import "CPSplitView.j" var CPSplitViewControllerAutomaticDimension = -1.0; /** * A container view controller that manages two or more child view * controllers in a split view interface. * * This class provides a controller-level abstraction for CPSplitView, * managing the addition, removal, and arrangement of view controllers * through CPSplitViewItem instances. * * This implementation was synthesized with the assistance of an LLM, * directed by the author. */ @implementation CPSplitViewController : CPViewController { /** @private The underlying split view that arranges the child views. */ CPSplitView _splitView; /** @private An array of CPSplitViewItem objects managed by this controller. */ CPMutableArray _splitViewItems; /** @private The minimum thickness for sidebars to be displayed inline. */ CPNumber _minimumThicknessForInlineSidebars; } // MARK: - Initialization - (id)init { if (self = [super init]) { _splitViewItems = [CPMutableArray array]; _minimumThicknessForInlineSidebars = 20.0; } return self; } // MARK: - View Lifecycle - (void)loadView { if (!_splitView) { _splitView = [[CPSplitView alloc] initWithFrame:CGRectMake(0,0,400,400)]; [_splitView setDelegate:self]; } [self setView:_splitView]; } - (void)viewDidLoad { [super viewDidLoad]; for (var i = 0; i < [_splitViewItems count]; i++) { var viewController = [[_splitViewItems objectAtIndex:i] viewController]; [[self splitView] addArrangedSubview:[viewController view]]; } } // MARK: - Accessors /** * Returns the CPSplitView instance managed by the controller. * * @returns {CPSplitView} The split view. */ - (CPSplitView)splitView { return _splitView; } /** * Sets a custom split view for the controller. * * @param {CPSplitView} splitView The custom split view to use. */ - (void)setSplitView:(CPSplitView)splitView { if (_splitView !== splitView) { _splitView = splitView; [_splitView setDelegate:self]; if ([self isViewLoaded]) [self setView:_splitView]; } } /** * Returns the array of split view items. * * @returns {CPArray} The array of CPSplitViewItem objects. */ - (CPArray)splitViewItems { return _splitViewItems; } /** * Sets the array of split view items, replacing any existing items. * * @param {CPArray} splitViewItems An array of CPSplitViewItem objects. */ - (void)setSplitViewItems:(CPArray)splitViewItems { // Remove all existing items while ([_splitViewItems count] > 0) { [self removeSplitViewItem:[_splitViewItems lastObject]]; } // Add new items for (var i = 0; i < [splitViewItems count]; i++) { [self addSplitViewItem:[splitViewItems objectAtIndex:i]]; } } // MARK: - Managing Split View Items /** * Adds a split view item to the end of the split view. * * @param {CPSplitViewItem} splitViewItem The split view item to add. */ - (void)addSplitViewItem:(CPSplitViewItem)splitViewItem { [self insertSplitViewItem:splitViewItem atIndex:[_splitViewItems count]]; } /** * Inserts a split view item at a specific index. * * @param {CPSplitViewItem} splitViewItem The split view item to insert. * @param {CPInteger} index The zero-based index at which to insert the item. */ - (void)insertSplitViewItem:(CPSplitViewItem)splitViewItem atIndex:(CPInteger)index { [splitViewItem _setSplitViewController:self]; [_splitViewItems insertObject:splitViewItem atIndex:index]; [self addChildViewController:[splitViewItem viewController]]; if ([self isViewLoaded]) [[self splitView] insertArrangedSubview:[[splitViewItem viewController] view] atIndex:index]; } /** * Removes the specified split view item. * * @param {CPSplitViewItem} splitViewItem The split view item to remove. */ - (void)removeSplitViewItem:(CPSplitViewItem)splitViewItem { var viewController = [splitViewItem viewController]; if ([self isViewLoaded]) [[viewController view] removeFromSuperview]; [viewController removeFromParentViewController]; [splitViewItem _setSplitViewController:nil]; [_splitViewItems removeObject:splitViewItem]; } /** * Retrieves the split view item associated with a given view controller. * * @param {CPViewController} viewController The view controller to find. * @returns {CPSplitViewItem | null} The corresponding split view item, or nil if not found. */ - (CPSplitViewItem)splitViewItemForViewController:(CPViewController)viewController { for (var i = 0; i < [_splitViewItems count]; i++) { var item = [_splitViewItems objectAtIndex:i]; if ([item viewController] === viewController) return item; } return nil; } // MARK: - Managing Sidebars and Inspectors /** * Toggles the collapsed state of the first split view item, typically a sidebar. * * @param {id} sender The object that initiated the action. */ - (void)toggleSidebar:(id)sender { if ([_splitViewItems count] > 0) { var sidebarItem = [_splitViewItems objectAtIndex:0]; [sidebarItem setCollapsed:![sidebarItem isCollapsed]]; } } /** * Toggles the collapsed state of the last split view item, typically an inspector. * * @param {id} sender The object that initiated the action. */ - (void)toggleInspector:(id)sender { if ([_splitViewItems count] > 1) { var inspectorItem = [_splitViewItems lastObject]; [inspectorItem setCollapsed:![inspectorItem isCollapsed]]; } } /** * Returns the minimum thickness for sidebars to be displayed inline. * * @returns {CPNumber} The minimum thickness. */ - (CPNumber)minimumThicknessForInlineSidebars { return _minimumThicknessForInlineSidebars; } // MARK: - CPSplitViewDelegate Methods // Note: A more complete implementation would forward these delegate methods // to a separate delegate property on the CPSplitViewController itself. // For now, these are stubbed to demonstrate where they would be handled. - (BOOL)splitView:(CPSplitView)splitView canCollapseSubview:(CPView)subview { // Default behavior: allow all subviews to be collapsed. return YES; } - (CGFloat)splitView:(CPSplitView)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(CPInteger)dividerIndex { return proposedMinimumPosition; } - (CGFloat)splitView:(CPSplitView)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(CPInteger)dividerIndex { return proposedMaximumPosition; } - (CGRect)splitView:(CPSplitView)splitView effectiveRect:(CGRect)proposedEffectiveRect forDrawnRect:(CGRect)drawnRect ofDividerAtIndex:(CPInteger)dividerIndex { // Default behavior: return the proposed rectangle. // This can be overridden to provide a larger or custom hit area for the divider. return proposedEffectiveRect; } - (void)splitViewDidResizeSubviews:(CPNotification)notification { // Can be used to respond to user-initiated resizing. } @end /** * An object that manages a view controller within a CPSplitViewController. * * A CPSplitViewItem acts as a wrapper around a CPViewController, * maintaining properties like its collapsed state within the parent * split view controller. */ @implementation CPSplitViewItem : CPObject { /** @private The view controller managed by this item. */ CPViewController _viewController; /** @private A boolean indicating whether the item is collapsed. */ BOOL _isCollapsed; /** @private A weak reference to the owning split view controller. */ CPSplitViewController _splitViewController; } // MARK: - Class Methods /** * Creates and returns a new split view item with the specified view controller. * * @param {CPViewController} viewController The view controller for the new item. * @returns {instancetype} A new CPSplitViewItem instance. */ + (instancetype)splitViewItemWithViewController:(CPViewController)viewController { return [[self alloc] initWithViewController:viewController]; } // MARK: - Initialization /** * Initializes a new split view item with the specified view controller. * * @param {CPViewController} viewController The view controller for the new item. * @returns {id} The initialized CPSplitViewItem instance. */ - (id)initWithViewController:(CPViewController)viewController { if (self = [super init]) { _viewController = viewController; _isCollapsed = NO; } return self; } // MARK: - Accessors /** * Returns the view controller associated with the item. * * @returns {CPViewController} The associated view controller. */ - (CPViewController)viewController { return _viewController; } /** * Returns a boolean value indicating whether the item is collapsed. * * @returns {BOOL} YES if the item is collapsed, otherwise NO. */ - (BOOL)isCollapsed { return _isCollapsed; } /** * Sets the collapsed state of the item. * When collapsed, the view controller's view is hidden. * * @param {BOOL} shouldCollapse YES to collapse the item, NO to expand it. */ - (void)setCollapsed:(BOOL)shouldCollapse { if (_isCollapsed === shouldCollapse) return; _isCollapsed = shouldCollapse; [[_viewController view] setHidden:shouldCollapse]; } /** * Returns the split view controller that owns this item. * * @returns {CPSplitViewController | null} The parent split view controller. */ - (CPSplitViewController)splitViewController { return _splitViewController; } /** * @private * Sets the owning split view controller. This method is for internal use by * CPSplitViewController. * * @param {CPSplitViewController} splitViewController The parent controller. */ - (void)_setSplitViewController:(CPSplitViewController)splitViewController { _splitViewController = splitViewController; } @end