diff --git a/Ladybird/AppKit/Application/ApplicationDelegate.mm b/Ladybird/AppKit/Application/ApplicationDelegate.mm index b4bc3df2d0..8b509b86b8 100644 --- a/Ladybird/AppKit/Application/ApplicationDelegate.mm +++ b/Ladybird/AppKit/Application/ApplicationDelegate.mm @@ -342,6 +342,9 @@ [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"View Source" action:@selector(viewSource:) keyEquivalent:@""]]; + [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Console" + action:@selector(openConsole:) + keyEquivalent:@"J"]]; [menu setSubmenu:submenu]; return menu; diff --git a/Ladybird/AppKit/UI/Console.h b/Ladybird/AppKit/UI/Console.h new file mode 100644 index 0000000000..7b46e2a05b --- /dev/null +++ b/Ladybird/AppKit/UI/Console.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#import + +@class LadybirdWebView; +@class Tab; + +@interface Console : NSWindow + +- (instancetype)init:(Tab*)tab; + +- (void)reset; + +@property (nonatomic, strong) LadybirdWebView* web_view; + +@end diff --git a/Ladybird/AppKit/UI/Console.mm b/Ladybird/AppKit/UI/Console.mm new file mode 100644 index 0000000000..65edc65e64 --- /dev/null +++ b/Ladybird/AppKit/UI/Console.mm @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +#import +#import +#import +#import + +#if !__has_feature(objc_arc) +# error "This project requires ARC" +#endif + +static constexpr CGFloat const WINDOW_WIDTH = 520; +static constexpr CGFloat const WINDOW_HEIGHT = 600; + +@interface Console () +{ + OwnPtr m_console_client; +} + +@property (nonatomic, strong) Tab* tab; +@property (nonatomic, strong) NSScrollView* scroll_view; + +@end + +@implementation Console + +@synthesize tab = _tab; + +- (instancetype)init:(Tab*)tab +{ + auto tab_rect = [tab frame]; + auto position_x = tab_rect.origin.x + (tab_rect.size.width - WINDOW_WIDTH) / 2; + auto position_y = tab_rect.origin.y + (tab_rect.size.height - WINDOW_HEIGHT) / 2; + + auto window_rect = NSMakeRect(position_x, position_y, WINDOW_WIDTH, WINDOW_HEIGHT); + auto style_mask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; + + self = [super initWithContentRect:window_rect + styleMask:style_mask + backing:NSBackingStoreBuffered + defer:NO]; + + if (self) { + self.tab = tab; + + self.web_view = [[LadybirdWebView alloc] init:nil]; + [self.web_view setPostsBoundsChangedNotifications:YES]; + + m_console_client = make([[tab web_view] view], [[self web_view] view]); + + self.scroll_view = [[NSScrollView alloc] initWithFrame:[self frame]]; + [self.scroll_view setHasVerticalScroller:YES]; + [self.scroll_view setHasHorizontalScroller:YES]; + [self.scroll_view setLineScroll:24]; + + [self.scroll_view setContentView:self.web_view]; + [self.scroll_view setDocumentView:[[NSView alloc] init]]; + + auto* font = [NSFont monospacedSystemFontOfSize:12.0 + weight:NSFontWeightRegular]; + + auto* prompt_indicator_attributes = @{ + NSForegroundColorAttributeName : [NSColor systemCyanColor], + NSFontAttributeName : font, + }; + auto* prompt_indicator_attribute = [[NSAttributedString alloc] initWithString:@">>" + attributes:prompt_indicator_attributes]; + auto* prompt_indicator = [NSTextField labelWithAttributedString:prompt_indicator_attribute]; + + auto* prompt_text = [[NSTextField alloc] init]; + [prompt_text setPlaceholderString:@"Enter JavaScript statement"]; + [prompt_text setDelegate:self]; + [prompt_text setBordered:YES]; + [prompt_text setBezeled:YES]; + [prompt_text setFont:font]; + + auto* clear_button = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameStopProgressTemplate] + target:self + action:@selector(clearConsole:)]; + [clear_button setToolTip:@"Clear the console output"]; + + auto* controls_stack_view = [NSStackView stackViewWithViews:@[ prompt_indicator, prompt_text, clear_button ]]; + [controls_stack_view setOrientation:NSUserInterfaceLayoutOrientationHorizontal]; + [controls_stack_view setEdgeInsets:NSEdgeInsetsMake(8, 8, 8, 8)]; + + auto* content_stack_view = [NSStackView stackViewWithViews:@[ self.scroll_view, controls_stack_view ]]; + [content_stack_view setOrientation:NSUserInterfaceLayoutOrientationVertical]; + [content_stack_view setSpacing:0]; + + [self setContentView:content_stack_view]; + [self setTitle:@"Console"]; + [self setIsVisible:YES]; + + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(onContentScroll:) + name:NSViewBoundsDidChangeNotification + object:[self.scroll_view contentView]]; + } + + return self; +} + +#pragma mark - Public methods + +- (void)reset +{ + m_console_client->reset(); +} + +#pragma mark - Private methods + +- (void)onContentScroll:(NSNotification*)notification +{ + [[self web_view] handleScroll]; +} + +- (void)clearConsole:(id)sender +{ + m_console_client->clear(); +} + +#pragma mark - NSTextFieldDelegate + +- (BOOL)control:(NSControl*)control + textView:(NSTextView*)text_view + doCommandBySelector:(SEL)selector +{ + if (selector != @selector(insertNewline:)) { + return NO; + } + + auto* ns_script = [[text_view textStorage] string]; + auto script = Ladybird::ns_string_to_string(ns_script); + + if (!script.bytes_as_string_view().is_whitespace()) { + m_console_client->execute(script); + [text_view setString:@""]; + } + + return YES; +} + +@end diff --git a/Ladybird/AppKit/UI/ConsoleController.h b/Ladybird/AppKit/UI/ConsoleController.h new file mode 100644 index 0000000000..dbee32761b --- /dev/null +++ b/Ladybird/AppKit/UI/ConsoleController.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#import + +@class Tab; + +@interface ConsoleController : NSWindowController + +- (instancetype)init:(Tab*)tab; + +@end diff --git a/Ladybird/AppKit/UI/ConsoleController.mm b/Ladybird/AppKit/UI/ConsoleController.mm new file mode 100644 index 0000000000..d7cdb97130 --- /dev/null +++ b/Ladybird/AppKit/UI/ConsoleController.mm @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#import +#import +#import +#import + +#if !__has_feature(objc_arc) +# error "This project requires ARC" +#endif + +@interface ConsoleController () + +@property (nonatomic, strong) Tab* tab; + +@end + +@implementation ConsoleController + +- (instancetype)init:(Tab*)tab +{ + if (self = [super init]) { + self.tab = tab; + } + + return self; +} + +#pragma mark - Private methods + +- (Console*)console +{ + return (Console*)[self window]; +} + +#pragma mark - NSWindowController + +- (IBAction)showWindow:(id)sender +{ + self.window = [[Console alloc] init:self.tab]; + [self.window setDelegate:self]; + [self.window makeKeyAndOrderFront:sender]; +} + +#pragma mark - NSWindowDelegate + +- (void)windowWillClose:(NSNotification*)notification +{ + [self.tab onConsoleClosed]; +} + +- (void)windowDidResize:(NSNotification*)notification +{ + if (![[self window] inLiveResize]) { + [[[self console] web_view] handleResize]; + } +} + +@end diff --git a/Ladybird/AppKit/UI/LadybirdWebView.h b/Ladybird/AppKit/UI/LadybirdWebView.h index 943e4fd4b4..54966fa5c5 100644 --- a/Ladybird/AppKit/UI/LadybirdWebView.h +++ b/Ladybird/AppKit/UI/LadybirdWebView.h @@ -10,6 +10,7 @@ #include #include #include +#include #import @@ -41,6 +42,7 @@ - (void)loadURL:(URL const&)url; - (void)loadHTML:(StringView)html url:(URL const&)url; +- (WebView::ViewImplementation&)view; - (String const&)handle; - (void)handleResize; diff --git a/Ladybird/AppKit/UI/LadybirdWebView.mm b/Ladybird/AppKit/UI/LadybirdWebView.mm index 8b8e43619c..e8e33bc6a0 100644 --- a/Ladybird/AppKit/UI/LadybirdWebView.mm +++ b/Ladybird/AppKit/UI/LadybirdWebView.mm @@ -112,6 +112,11 @@ struct HideCursor { m_web_view_bridge->load_html(html, url); } +- (WebView::ViewImplementation&)view +{ + return *m_web_view_bridge; +} + - (String const&)handle { return m_web_view_bridge->handle(); diff --git a/Ladybird/AppKit/UI/Tab.h b/Ladybird/AppKit/UI/Tab.h index fad691f05a..d60e85892b 100644 --- a/Ladybird/AppKit/UI/Tab.h +++ b/Ladybird/AppKit/UI/Tab.h @@ -12,6 +12,11 @@ @interface Tab : NSWindow +- (void)tabWillClose; + +- (void)openConsole:(id)sender; +- (void)onConsoleClosed; + @property (nonatomic, strong) LadybirdWebView* web_view; @end diff --git a/Ladybird/AppKit/UI/Tab.mm b/Ladybird/AppKit/UI/Tab.mm index 7d63fb4dac..cbf1a83e50 100644 --- a/Ladybird/AppKit/UI/Tab.mm +++ b/Ladybird/AppKit/UI/Tab.mm @@ -12,6 +12,8 @@ #include #import +#import +#import #import #import #import @@ -29,6 +31,8 @@ static constexpr CGFloat const WINDOW_HEIGHT = 800; @property (nonatomic, strong) NSString* title; @property (nonatomic, strong) NSImage* favicon; +@property (nonatomic, strong) ConsoleController* console_controller; + @end @implementation Tab @@ -95,6 +99,31 @@ static constexpr CGFloat const WINDOW_HEIGHT = 800; return self; } +#pragma mark - Public methods + +- (void)tabWillClose +{ + if (self.console_controller != nil) { + [self.console_controller.window close]; + } +} + +- (void)openConsole:(id)sender +{ + if (self.console_controller != nil) { + [self.console_controller.window makeKeyAndOrderFront:sender]; + return; + } + + self.console_controller = [[ConsoleController alloc] init:self]; + [self.console_controller showWindow:nil]; +} + +- (void)onConsoleClosed +{ + self.console_controller = nil; +} + #pragma mark - Private methods - (TabController*)tabController @@ -185,6 +214,11 @@ static constexpr CGFloat const WINDOW_HEIGHT = 800; self.title = Ladybird::string_to_ns_string(url.serialize()); self.favicon = [Tab defaultFavicon]; [self updateTabTitleAndFavicon]; + + if (self.console_controller != nil) { + auto* console = (Console*)[self.console_controller window]; + [console reset]; + } } - (void)onTitleChange:(DeprecatedString const&)title diff --git a/Ladybird/AppKit/UI/TabController.mm b/Ladybird/AppKit/UI/TabController.mm index dfc9de28c8..b09a443402 100644 --- a/Ladybird/AppKit/UI/TabController.mm +++ b/Ladybird/AppKit/UI/TabController.mm @@ -334,6 +334,8 @@ enum class IsHistoryNavigation { - (void)windowWillClose:(NSNotification*)notification { + [[self tab] tabWillClose]; + auto* delegate = (ApplicationDelegate*)[NSApp delegate]; [delegate removeTab:self]; } diff --git a/Ladybird/CMakeLists.txt b/Ladybird/CMakeLists.txt index e849b38dd7..821eaa18c3 100644 --- a/Ladybird/CMakeLists.txt +++ b/Ladybird/CMakeLists.txt @@ -133,6 +133,8 @@ elseif (APPLE) AppKit/Application/Application.mm AppKit/Application/ApplicationDelegate.mm AppKit/Application/EventLoopImplementation.mm + AppKit/UI/Console.mm + AppKit/UI/ConsoleController.mm AppKit/UI/Event.mm AppKit/UI/LadybirdWebView.mm AppKit/UI/LadybirdWebViewBridge.cpp