Disable Qt quit-on-last-window behavior on macOS and install an
application-level menu so useful commands remain available after the
last browser window closes.
Reuse shared application actions and menus for Settings, Bookmarks,
History, Help, and global appearance controls. Centralize window-aware
commands such as New Tab, New Window, Open File, Open Location, and
Reopen Recently Closed so the browser window and windowless app menu
use the same behavior. Keep the location editor focused for new blank
windows after the native window has been activated.
Disable AppKit's native resize handling for client-side decorated macOS
windows so AppKit does not show a resize cursor for points it will not
deliver to Ladybird. Reapply this after Qt creates the native surface,
since Qt may restore the resizable style mask during Cocoa window setup.
Handle the resize gesture in BrowserWindow instead, with thin side edges
and a slightly larger corner target that avoids the rounded corner
cutouts.
Previously, the "View History" action always opened a new tab. We now
switch to an existing `about:history` tab when one exists in the active
window and fall back to opening a new tab otherwise.
When using client-side decorations, the window was a flat rectangle
with no outline, so its edge disappeared against similarly colored
backgrounds. Paint a 1px border in a new chrome window outline color
and reserve a matching 1px contents margin so child widgets do not
cover it.
The outline color matches chrome_border() in dark mode, but is darker
in light mode, since the window edge has to hold up against arbitrary
backdrops behind the window rather than our own chrome surfaces.
The border is skipped when the window is maximized or fullscreen, and
on macOS, where frameless windows already get rounded corners and a
native shadow.
This currently just contains a menu item to open about:history. But in
the future, we can add a list of recently closed / recently visited
pages as well.
We currently use LibWebView's Application as the entry point to learn
about the bookmarks bar being shown/hidden, and propagate that through
virtual methods. At the time this was added, AppKit's Tab window and
Qt's BrowserWindow did not have a settings observer. They do now, so
let's skip a couple of middle-men.
For AppKit, we change the settings observer to just (weakly) store the
Tab instance so that we don't have to add callback functions for each
setting.
Hide fullscreen chrome at the TabWidget level instead of hiding the
current tab's toolbar child directly. This lets the toolbar container
and reserved verticaltabs space stop contributing to layout while
fullscreen content is shown.
Also keep the exit fullscreen button above native web content on macOS
and center it using the fullscreen screen geometry rather than a
potentially stale parent widget size.
Do not clear the Qt window mask from the non-macOS corner update path.
The custom rounded-corner implementation is macOS-only, so the Linux
resize path should not touch native mask state on every resize event.
Also make dynamic property updates idempotent for tab chrome widgets.
Vertical tab resize and hover updates now only repolish when a property
actually changes, and avoid reapplying unchanged New Tab button text,
style, and size.
Treat the toolbar, tab strip, and vertical sidebar as one browser
chrome surface. Add an explicit layout policy for platform window
controls and sidebar widths. This lets macOS use left-side traffic
lights while other platforms keep right-side custom controls.
Keep the macOS toolbar stable in vertical-tabs mode. Traffic lights and
navigation controls stay in the top row, and the sidebar sits below it.
Draw chrome/content separators explicitly so the content edge remains
clear without adding seams inside the chrome surface.
Retune tab, toolbar, and omnibox styling for the unified chrome. Tabs
use card-like selected states, quiet inactive states, and matching
horizontal and vertical geometry.
Make hover-expanded vertical tabs draw as a chrome overlay so the web
content does not relayout. Keep the overlay above native web views and
route macOS window drags through AppKit events for reliable dragging
from custom chrome.
We currently rely on QStyle icons to draw the audio state icons. But
these icons are system dependent. The macOS icons look particularly
out-of-place here. So let's draw chrome icons.
This as a pesudo third mode to vertical tabs. Now when the vertical tabs
are collapsed, users can enable a setting that will expand the vertical
tab area on hover. Unlike full expansion, the hovered vertical tab area
will cover the web content area to avoid pushing the web content area
around.
Using the status bar for this meant that the "DevTools is enabled on
port X" message would get overwritten whenever the user hovered over a
menu item. We don't otherwise use or need a status bar, so replace it
with an explicit banner widget that's only used for this purpose. Keep
the existing styling so it looks the same as before.
The macOS rounded-corner helper needs the native view to be attached to
an NSWindow. The constructor can run too early for that, leaving startup
windows square even though the setting is enabled.
Reapply the corner state after Qt creates the platform surface, deferred
until Cocoa has attached the view to the native window.
Support Command+Shift+[ and Command+Shift+] for moving between
Qt tabs on macOS. Also support the physical Control+Tab and
Control+Shift+Tab shortcuts accepted by other browsers.
Treat these as browser-reserved shortcuts so WebContent does not
consume the shortcut override before the browser action can run.
Add an advanced setting for rounded browser window corners. On macOS,
Qt browser windows now use a layer-backed corner radius, and clear it
when maximized, fullscreen, or the setting is disabled.
Hide the setting from about:settings on other platforms for now since Qt
does not expose a native antialiased window corner API there.
Refine vertical tab sidebar styling. Let inactive tabs sit on chrome.
Keep close buttons quiet. Align the New Tab row with tab rows.
Persist the expanded sidebar width and keep resizing bounded. Put the
resize hit area over the divider. Keep the collapsed rail fixed-width.
Avoid explicit mouse grabs while dragging.
Paint subtle sidebar dividers. Move the expand/collapse control into
the navigation toolbar. Tune the fallback globe and vertical-tabs icons.
Move custom window controls into the navigation toolbar while vertical
tabs are enabled. Remove the separate top row that only held them.
Mark empty toolbar space as draggable so the unified row acts like the
titlebar. Keep horizontal tab mode on the existing tab strip controls.
This implements an initial version of vertical tabs for the Qt UI. Users
may enable vertical tabs in about:settings. When enabled, a button is
added to the toolbar to expand/collapse the vertical tabs.
There is plenty more to do here. We will want to support expanding on
hover, drawing the close tab / audio button indicators on collapsed
vertical tabs, positioning the vetical tab bar, etc.
The browser process forwards the maximum frame rate to WebContent, but
the compositor process had no copy of that display timing metadata or
the display identity needed by compositor-local vsync.
Add one compositor control IPC message carrying the optional display ID
and refresh rate together, and store both values on each compositor
context.
BrowserWindow only connected QWindow::screenChanged while setting up
the non-Wayland DPI path. On Qt Wayland this path is skipped
because device-pixel-ratio changes are delivered to the window instead
of QScreen. A window could stay associated with the screen reported
during construction until the native QWindow exists, so refresh-rate
updates were missed when the window moved between outputs.
Separate refresh-rate and screen tracking from the DPI-specific signal
handling. Connect QScreen::refreshRateChanged for the current screen on
all platforms, connect QWindow::screenChanged when the native window
becomes available, and use ScreenChangeInternal as an additional update
hook. The DPI signal remains disabled for Qt Wayland, where
DevicePixelRatioChange is still handled through BrowserWindow::event().
Before the recent Qt overhaul, we had no control over the position of
the tab close button (without drawing/maintaining it ourselves, which we
now do). Qt automatically placed the tab close button on the left side
of the tab bar on macOS, and as such, we had to go out of our way to
place the audio state button on the right side. See:
dd54780d5e
We now consistently place the audio state button on the left and the tab
close button on the right. Qt still attempts to draw its own tab close
button on the left, so we also explicitly set that to null initially.
Add a menu bar corner widget for the frameless window controls when
the Qt menu bar is visible. Hide the tab strip controls in that mode
so the buttons stay at the top-right edge of the window, and remove
the separator below the menu bar so the chrome reads as one surface.
Use compact menu bar controls and keep the right padding tight so the
buttons align with the edge without changing the hidden-menu layout.
Start a platform window move when pressing empty space in the Qt menu
bar. Toggle maximized state on double-click, matching the tab strip.
Menu actions keep their normal behavior, and resize edges still take
precedence before starting a move.
Let application actions opt out of icon creation when created for
menus. Use that path for browser menus, tab context menus, and
WebView-backed popup menus.
Keep icon creation for toolbar and bookmarks bar actions, where the
same helper still provides visible button icons. Remove the icon setup
that is now only used by menu actions, and shrink popup menu item
padding now that there is no icon column.
Add a QMenuBar stylesheet that uses the same chrome palette as the tab
strip and navigation toolbar. Apply it from BrowserWindow and refresh it
when the palette changes so the optional menu bar matches the frameless
window chrome.
Send hidden and visible system visibility updates from explicit tab
switch paths instead of relying only on widget show and hide events. Tab
containers can keep inactive web views mapped, which leaves documents
visible from WebContent's perspective and lets animated images keep
running in background tabs.
Use AppKit window occlusion state rather than the app-wide active tab.
That keeps still-visible background windows in the visible document
state. Avoid resending duplicate visibility updates from
ViewImplementation so existing widget visibility notifications remain
harmless.
Draw back and forward icons as filled stroked paths so the arrow tips do
not accumulate alpha where segments meet. Increase their apparent size,
shift the menu glyph down slightly, and raise the new tab icon to align
with the tab strip.
Refine the material colors, tab integration, toolbar spacing, and
custom glyphs used by the Qt browser chrome. Keep the visual density
compact while making the controls feel clearer and less toolkit-like.
Mark the window opaque and show resize cursors for frameless chrome.
Allow custom Qt tabs to be reordered, detached, and reparented to
other browser windows while preserving the existing tab state. Draw a
full-tab drag preview and keep tab drags from being interpreted as
frameless window moves.
Add shared chrome styling helpers and use them to paint the Linux
browser tab strip and surrounding toolbar widgets. Make the browser
window frameless on Qt so the tabs, toolbar, and window controls can
share one integrated chrome surface.
Show visible tab icons as loading while the focused location field
keeps its existing page-level loading state. Cache the favicon under the
spinner so each tab restores its page icon when loading finishes.
Reuse the Qt spinner drawing for the location field and tab strip, and
draw the AppKit spinner into the attributed tab title.
Create new windows with the size of the previously active window, and
maximized if it was maximized. This matches the behaviour of other
browsers, and solves an annoying issue where new windows would always
be tiny.
Application::new_window() now takes a WindowConfiguration struct that
lets you specify the position, size, and maximized-ness of the window.
All previous callers of new_window() now use this instead of modifying
the window state afterwards.
WindowConfiguration is slightly awkwardly broken up into individual x/y
and width/height fields instead of a position and a size, for the sake
of compatibility with HTML::WebViewHints.
Restore the active tab's remembered focus widget when switching tabs.
If the tab has no remembered focused child, fall back to the web
view so keyboard scrolling works immediately in tabs opened from
links.
This keeps the existing new-tab location bar behavior while making
background-opened pages respond to keyboard inputs as soon as they
are activated.
In addition to Ctrl+PageDown and Ctrl+PageUp, also support Ctrl+Tab and
Ctrl+Shift+Tab to switch between browser tabs. This is the common
behavior in other browsers.
QTabWidget's built-in tab bar management made it difficult to properly
position the new tab button, and caused visual artifacts with the
separator line across themes.
Replace QTabWidget with a custom TabWidget that places a QTabBar
alongside an explicit new tab button. This us gives full control over
the tab bar row layout.
The HamburgerMenu subclass overrode showEvent() to reposition the menu
using move() after QMenu::popup() had already shown it. On the first
open, rect().width() could return a stale value, causing the menu to
appear detached from the button and truncated to a single item. The
move() call also bypassed Qt's screen boundary adjustments.
Instead, we can replace the HamburgerMenu with a normal QMenu, and use a
custom HamburgerButton class for the menu's tool button. In this class,
we override mousePressEvent() to call popup() with the correct right-
aligned position before the menu is shown. This lets Qt manage sizing
and screen boundaries correctly.
This adds the following features to the Qt UI:
- An "exit fullscreen" button that displays when entering fullscreen
This will disappear after a certain amount of time. Moving the
cursor to the top of the screen will make it reappear.
- Switching tabs exits fullscreen
The look and feel of this is preliminary and should be expanded upon
in the future.