From 4a76f9fbda1c00a6d957e666834ec98a70c85617 Mon Sep 17 00:00:00 2001 From: uint Date: Thu, 18 Dec 2025 19:03:10 +0000 Subject: revamp browser, now using sdl3 + ladybird engine the browser now draws a box with some fairly simple c++ code SDL3 because its a much clearer API than SDL2 . . --- .gitignore | 1 - Makefile | 103 ++++++-- README.md | 8 +- quartz | Bin 31672 -> 0 bytes quartz/CMakeLists.txt | 27 ++ quartz/quartz.cpp | 690 ++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 809 insertions(+), 20 deletions(-) delete mode 100755 quartz create mode 100644 quartz/CMakeLists.txt create mode 100644 quartz/quartz.cpp diff --git a/.gitignore b/.gitignore index 63deaf4..1cbbf9c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ -quark *.o compile_flags.txt diff --git a/Makefile b/Makefile index 7151105..443c894 100644 --- a/Makefile +++ b/Makefile @@ -1,28 +1,97 @@ -CC = cc +# tooling +CXX = c++ +CXXFLAGS = -std=c++23 -Os -Wall -Wextra +PKG_CONFIG = pkg-config -DEPS = -lSDL2 -lGLESv2 -INCLUDE = -Iinclude -CFLAGS = -std=c99 -O2 -Wall -Wextra $(INCLUDE) -LDFLAGS = $(DEPS) +# CHANGE THIS: ladybird source tree +LADYBIRD = $(HOME)/clones/ladybird-test +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +BUILD = $(LADYBIRD)/Build/release +INCS = -I$(LADYBIRD) -I$(LADYBIRD)/Services -I$(LADYBIRD)/Libraries \ + -I$(BUILD)/Lagom -I$(BUILD)/Lagom/Services -I$(BUILD)/Lagom/Libraries \ + -I$(BUILD)/vcpkg_installed/x64-linux-dynamic/include \ + $$($(PKG_CONFIG) --cflags sdl3 2>/dev/null) -SRC = src/quartz.c src/sdl.c src/render.c src/engine.c -OBJ = build/quartz.o build/sdl.o build/render.o build/engine.o +# ladybird libs +LAGOM_LIBS = -L$(BUILD)/lib \ + -llagom-webview \ + -llagom-web \ + -llagom-js \ + -llagom-gfx \ + -llagom-url \ + -llagom-core \ + -llagom-coreminimal \ + -llagom-ak \ + -llagom-ipc \ + -llagom-unicode \ + -llagom-textcodec \ + -llagom-gc \ + -llagom-requests \ + -llagom-http \ + -llagom-crypto \ + -llagom-tls \ + -llagom-compress \ + -llagom-xml \ + -llagom-wasm \ + -llagom-media \ + -llagom-imagedecoderclient \ + -llagom-devtools \ + -llagom-database \ + -llagom-filesystem \ + -llagom-syntax \ + -llagom-idl -all: quartz +# external libs +EXT_LIBS = $$($(PKG_CONFIG) --libs sdl3 sdl3-ttf 2>/dev/null) \ + -L$(BUILD)/vcpkg_installed/x64-linux-dynamic/lib \ + -lfontconfig -quartz: $(OBJ) - $(CC) -o quartz $(OBJ) $(LDFLAGS) +# main() wrapper (static) +MAIN_LIB = $(BUILD)/lib/liblagom-main.a +LIBS = $(MAIN_LIB) $(LAGOM_LIBS) $(EXT_LIBS) -lpthread -build/%.o: src/%.c - mkdir -p build - $(CC) $(CFLAGS) -c $< -o $@ +# rpath +LDFLAGS = -Wl,-rpath,$(BUILD)/lib -Wl,-rpath,$(BUILD)/vcpkg_installed/x64-linux-dynamic/lib + +# sources +SRCDIR = quartz +SRC = $(SRCDIR)/quartz.cpp +OBJ = $(SRC:.cpp=.o) +BIN = quartz/quartz + +all: $(BIN) + +$(BIN): $(OBJ) + $(CXX) $(LDFLAGS) -o $@ $(OBJ) $(LIBS) + +$(SRCDIR)/%.o: $(SRCDIR)/%.cpp + $(CXX) $(CXXFLAGS) $(INCS) -c $< -o $@ clean: - rm -f quartz $(OBJ) + rm -f $(OBJ) $(BIN) -run: +install: $(BIN) + cp $(BIN) $(BUILD)/bin/ + +run: install + cd $(BUILD)/bin && \ + LD_LIBRARY_PATH=$(BUILD)/lib:$(BUILD)/vcpkg_installed/x64-linux-dynamic/lib \ ./quartz -compile_flags: +patch: + cd $(LADYBIRD) && \ + git apply $$PWD/build_patches/cmake.patch && \ + git apply $$PWD/build_patches/lagom_options.patch && \ + git apply $$PWD/build_patches/vcpkg.patch && \ + git apply $$PWD/build_patches/ui_cmake.patch && \ + rm -f $(LADYBIRD)/UI/quartz && \ + ln -sf $$PWD/quartz $(LADYBIRD)/UI/quartz + +ladybird: + cd $(LADYBIRD) && \ + cmake --preset Release -DENABLE_QT=OFF -DENABLE_QUARTZ=ON && \ + ninja -C Build/release + +clangd: rm -f compile_flags.txt - for f in ${CFLAGS}; do echo $$f >> compile_flags.txt; done + for f in $(CXXFLAGS) $(INCS); do echo $$f >> compile_flags.txt; done diff --git a/README.md b/README.md index 5c678b4..155a45d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ -# quartz +# quartz browser -more suckless web-browser +## prerequisites + +- ladybird source tree (https://github.com/LadybirdBrowser/ladybird) +- sdl3, sdl3_ttf +- c++23 compiler diff --git a/quartz b/quartz deleted file mode 100755 index 79cf700..0000000 Binary files a/quartz and /dev/null differ diff --git a/quartz/CMakeLists.txt b/quartz/CMakeLists.txt new file mode 100644 index 0000000..e044721 --- /dev/null +++ b/quartz/CMakeLists.txt @@ -0,0 +1,27 @@ +find_package(SDL3 REQUIRED CONFIG) +find_package(PkgConfig REQUIRED) +pkg_check_modules(SDL3_TTF REQUIRED IMPORTED_TARGET sdl3-ttf) + +add_executable(ladybird + quartz.cpp + engine.cpp + ui.cpp + window.cpp +) + +target_link_libraries(ladybird PRIVATE + AK + LibCore + LibFileSystem + LibGfx + LibImageDecoderClient + LibMain + LibURL + LibWebView + SDL3::SDL3 + PkgConfig::SDL3_TTF +) + +target_include_directories(ladybird PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +create_ladybird_bundle(ladybird) diff --git a/quartz/quartz.cpp b/quartz/quartz.cpp new file mode 100644 index 0000000..533054c --- /dev/null +++ b/quartz/quartz.cpp @@ -0,0 +1,690 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +struct quartz; +struct simple_view; + +static quartz* g_app = NULL; +static simple_view* g_view = NULL; + +static SDL_SystemCursor cursor_to_sdl(Gfx::StandardCursor c) +{ + switch (c) { + case Gfx::StandardCursor::Arrow: + return SDL_SYSTEM_CURSOR_DEFAULT; + case Gfx::StandardCursor::IBeam: + return SDL_SYSTEM_CURSOR_TEXT; + case Gfx::StandardCursor::Hand: + return SDL_SYSTEM_CURSOR_POINTER; + case Gfx::StandardCursor::Crosshair: + return SDL_SYSTEM_CURSOR_CROSSHAIR; + case Gfx::StandardCursor::Wait: + return SDL_SYSTEM_CURSOR_WAIT; + case Gfx::StandardCursor::ResizeHorizontal: + return SDL_SYSTEM_CURSOR_EW_RESIZE; + case Gfx::StandardCursor::ResizeVertical: + return SDL_SYSTEM_CURSOR_NS_RESIZE; + case Gfx::StandardCursor::ResizeDiagonalTLBR: + return SDL_SYSTEM_CURSOR_NWSE_RESIZE; + case Gfx::StandardCursor::ResizeDiagonalBLTR: + return SDL_SYSTEM_CURSOR_NESW_RESIZE; + case Gfx::StandardCursor::Disallowed: + return SDL_SYSTEM_CURSOR_NOT_ALLOWED; + case Gfx::StandardCursor::Move: + return SDL_SYSTEM_CURSOR_MOVE; + default: + return SDL_SYSTEM_CURSOR_DEFAULT; + } +} + +static void set_cursor(Gfx::StandardCursor c) +{ + static SDL_Cursor* s_cursor = NULL; + SDL_Cursor* cur = SDL_CreateSystemCursor(cursor_to_sdl(c)); + if (!cur) + return; + + SDL_SetCursor(cur); + + if (s_cursor) + SDL_DestroyCursor(s_cursor); + + s_cursor = cur; +} + +static Web::UIEvents::KeyCode key_from_sdl(i32 k); +static Web::UIEvents::KeyModifier mods_from_sdl(u16 m); +static Web::UIEvents::MouseButton btn_from_sdl(u8 b); +static Web::UIEvents::MouseButton btns_from_sdl(u32 b); + +static void handle_sdl_event(simple_view* v, SDL_Event const* ev); +static void on_ev_tick(); +static void on_paint_tick(); + +static void on_title_change_cb(AK::Utf16String const& t); +static void on_ready_to_paint_cb(); +static void on_cursor_change_cb(AK::Variant const& cursor); + +struct simple_view : public WebView::ViewImplementation { + SDL_Window* win; + SDL_Renderer* ren; + SDL_Texture* tex; + + Gfx::IntSize vp_size; + int w; + int h; + + bool is_active; + bool is_dirty; + + /* expose ViewImplementation API (is protected) */ + using ViewImplementation::client; + using ViewImplementation::enqueue_input_event; + using ViewImplementation::traverse_the_history_by_delta; + + simple_view(SDL_Window* window, SDL_Renderer* renderer) + : win(window), ren(renderer), tex(NULL), w(0), h(0), + is_active(false), is_dirty(true) + { + SDL_GetWindowSize(win, &w, &h); + m_device_pixel_ratio = 1.0f; + + /* single instance callbacks */ + g_view = this; + ViewImplementation::on_title_change = on_title_change_cb; + ViewImplementation::on_ready_to_paint = on_ready_to_paint_cb; + ViewImplementation::on_cursor_change = on_cursor_change_cb; + + initialize_client(CreateNewClient::Yes); + } + + ~simple_view() override + { + if (tex) + SDL_DestroyTexture(tex); + } + + void update_screen_rects() + { + SDL_Rect bounds; + + if (!SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &bounds)) + return; + + Vector rects; + rects.append(Web::DevicePixelRect(bounds.x, bounds.y, bounds.w, bounds.h)); + client().async_update_screen_rects(m_client_state.page_index, rects, 0); + } + + void update_viewport() + { + int sw = (int)(w * m_device_pixel_ratio); + int sh = (int)(h * m_device_pixel_ratio); + + vp_size = { sw, sh }; + + client().async_set_viewport_size( + m_client_state.page_index, + vp_size.to_type()); + + handle_resize(); + } + + void initialize_client(CreateNewClient create_new) override + { + ViewImplementation::initialize_client(create_new); + + NonnullRefPtr ini = MUST(Core::Resource::load_from_uri("resource://themes/Default.ini"_string)); + Core::AnonymousBuffer theme = Gfx::load_system_theme(ini->filesystem_path().to_byte_string()) + .release_value_but_fixme_should_propagate_errors(); + + client().async_update_system_theme(m_client_state.page_index, theme); + + update_viewport(); + update_screen_rects(); + client().async_set_window_size(m_client_state.page_index, viewport_size()); + } + + void update_zoom() override + { + ViewImplementation::update_zoom(); + update_viewport(); + } + + Web::DevicePixelSize viewport_size() const override + { + return vp_size.to_type(); + } + + Gfx::IntPoint to_content_position(Gfx::IntPoint p) const override + { + return p; + } + + Gfx::IntPoint to_widget_position(Gfx::IntPoint p) const override + { + return p; + } + + void mark_dirty() + { + is_dirty = true; + } + + bool consume_dirty() + { + bool d = is_dirty; + is_dirty = false; + return d; + } + + void resize(int width, int height) + { + w = width; + h = height; + + update_viewport(); + mark_dirty(); + + if (tex) { + SDL_DestroyTexture(tex); + tex = NULL; + } + } + + void set_active(bool active) + { + is_active = active; + + set_system_visibility_state( + active ? Web::HTML::VisibilityState::Visible : Web::HTML::VisibilityState::Hidden + ); + + if (active) + client().async_set_has_focus(m_client_state.page_index, true); + } + + u64 page_idx() const + { + return m_client_state.page_index; + } + + void paint() + { + Gfx::Bitmap const* bmp = NULL; + Gfx::IntSize bmp_size; + + if (m_client_state.has_usable_bitmap) { + bmp = m_client_state.front_bitmap.bitmap.ptr(); + bmp_size = m_client_state.front_bitmap.last_painted_size.to_type(); + } + else if (m_backup_bitmap) { + bmp = m_backup_bitmap.ptr(); + bmp_size = m_backup_bitmap_size.to_type(); + } + + SDL_SetRenderDrawColor(ren, 255, 255, 255, 255); + SDL_RenderClear(ren); + + if (bmp) { + bool need_tex = !tex; + + if (tex) { + float tw = 0.0f, th = 0.0f; + SDL_GetTextureSize(tex, &tw, &th); + + if ((int)tw != bmp_size.width() || (int)th != bmp_size.height()) + need_tex = true; + } + + if (need_tex) { + if (tex) + SDL_DestroyTexture(tex); + + tex = SDL_CreateTexture( + ren, + SDL_PIXELFORMAT_BGRA32, + SDL_TEXTUREACCESS_STREAMING, + bmp_size.width(), + bmp_size.height()); + } + + if (tex) { + SDL_UpdateTexture(tex, NULL, bmp->scanline_u8(0), bmp->pitch()); + + int win_w = 0, win_h = 0; + SDL_GetWindowSize(win, &win_w, &win_h); + + SDL_FRect dst = { 0.0f, 0.0f, (float)win_w, (float)win_h }; + SDL_RenderTexture(ren, tex, NULL, &dst); + } + } + SDL_RenderPresent(ren); + } +}; + +struct quartz : public WebView::Application { /* this is a disgrace */ + WEB_VIEW_APPLICATION(quartz) + +public: + SDL_Window* win; + SDL_Renderer* ren; + simple_view* view; + + quartz() : win(NULL), ren(NULL), view(NULL) + { + /* nothing here */ + } + + ~quartz() override + { + if (view) + delete view; + + if (ren) + SDL_DestroyRenderer(ren); + + if (win) + SDL_DestroyWindow(win); + + SDL_Quit(); + } + + void create_platform_options(WebView::BrowserOptions&, WebView::RequestServerOptions&, WebView::WebContentOptions&) override + { + /* nothing here */ + } + + NonnullOwnPtr create_platform_event_loop() override + { + if (!SDL_Init(SDL_INIT_VIDEO)) { + warnln("sdl init failed: {}", SDL_GetError()); + std::exit(1); + } + + win = SDL_CreateWindow("Ladybird", 900, 650, SDL_WINDOW_RESIZABLE); + if (!win) { + warnln("window create failed: {}", SDL_GetError()); + SDL_Quit(); + std::exit(1); + } + + ren = SDL_CreateRenderer(win, NULL); + if (!ren) { + warnln("renderer create failed: {}", SDL_GetError()); + SDL_DestroyWindow(win); + SDL_Quit(); + std::exit(1); + } + + SDL_StartTextInput(win); + + return WebView::Application::create_platform_event_loop(); + } + + Optional active_web_view() const override + { + if (view) + return *view; + return {}; + } + + Utf16String clipboard_text() const override + { + if (!SDL_HasClipboardText()) + return {}; + + char* txt = SDL_GetClipboardText(); + if (!txt) + return {}; + + Utf16String result = AK::Utf16String::from_utf8(StringView { txt, std::strlen(txt) }); + SDL_free(txt); + return result; + } + + Vector clipboard_entries() const override + { + Vector entries; + + if (!SDL_HasClipboardText()) + return entries; + + char* txt = SDL_GetClipboardText(); + if (!txt) + return entries; + + entries.empend(ByteString { txt, std::strlen(txt) }, "text/plain"_string); + SDL_free(txt); + return entries; + } + + void insert_clipboard_entry(Web::Clipboard::SystemClipboardRepresentation entry) override + { + if (entry.mime_type == "text/plain"sv) + SDL_SetClipboardText(entry.data.characters()); + } +}; + +static void on_title_change_cb(AK::Utf16String const& t) +{ + if (!g_view || !g_view->win) + return; + + /* best-effort title: if conversion fails, keep old title */ + ByteString bs_or = t.to_byte_string(); + if (!bs_or.is_empty()) + SDL_SetWindowTitle(g_view->win, bs_or.characters()); + + g_view->mark_dirty(); +} + +static void on_ready_to_paint_cb() +{ + if (!g_view) + return; + + g_view->mark_dirty(); +} + +static void on_cursor_change_cb(AK::Variant const& cursor) +{ + struct cursor_visitor { + void operator()(Gfx::StandardCursor c) const { set_cursor(c); } + void operator()(Gfx::ImageCursor const&) const { } + }; + + cursor.visit(cursor_visitor {}); +} + +static Web::UIEvents::KeyCode key_from_sdl(i32 k) +{ + switch (k) { + case SDLK_BACKSPACE: + return Web::UIEvents::Key_Backspace; + case SDLK_TAB: + return Web::UIEvents::Key_Tab; + case SDLK_RETURN: + return Web::UIEvents::Key_Return; + case SDLK_ESCAPE: + return Web::UIEvents::Key_Escape; + case SDLK_SPACE: + return Web::UIEvents::Key_Space; + case SDLK_LEFT: + return Web::UIEvents::Key_Left; + case SDLK_RIGHT: + return Web::UIEvents::Key_Right; + case SDLK_UP: + return Web::UIEvents::Key_Up; + case SDLK_DOWN: + return Web::UIEvents::Key_Down; + case SDLK_HOME: + return Web::UIEvents::Key_Home; + case SDLK_END: + return Web::UIEvents::Key_End; + case SDLK_PAGEUP: + return Web::UIEvents::Key_PageUp; + case SDLK_PAGEDOWN: + return Web::UIEvents::Key_PageDown; + case SDLK_DELETE: + return Web::UIEvents::Key_Delete; + default: + if (k >= (i32)SDLK_A && k <= (i32)SDLK_Z) + return (Web::UIEvents::KeyCode)(Web::UIEvents::Key_A + (k - (i32)SDLK_A)); + if (k >= (i32)SDLK_0 && k <= (i32)SDLK_9) + return (Web::UIEvents::KeyCode)(Web::UIEvents::Key_0 + (k - (i32)SDLK_0)); + return Web::UIEvents::Key_Invalid; + } +} + +static Web::UIEvents::KeyModifier mods_from_sdl(u16 m) +{ + Web::UIEvents::KeyModifier r = Web::UIEvents::KeyModifier::Mod_None; + + if (m & SDL_KMOD_SHIFT) + r |= Web::UIEvents::KeyModifier::Mod_Shift; + + if (m & SDL_KMOD_CTRL) + r |= Web::UIEvents::KeyModifier::Mod_Ctrl; + + if (m & SDL_KMOD_ALT) + r |= Web::UIEvents::KeyModifier::Mod_Alt; + + return r; +} + +static Web::UIEvents::MouseButton btn_from_sdl(u8 b) +{ + switch (b) { + case SDL_BUTTON_LEFT: + return Web::UIEvents::MouseButton::Primary; + case SDL_BUTTON_RIGHT: + return Web::UIEvents::MouseButton::Secondary; + case SDL_BUTTON_MIDDLE: + return Web::UIEvents::MouseButton::Middle; + case SDL_BUTTON_X1: + return Web::UIEvents::MouseButton::Backward; + case SDL_BUTTON_X2: + return Web::UIEvents::MouseButton::Forward; + default: + return Web::UIEvents::MouseButton::None; + } +} + +static Web::UIEvents::MouseButton btns_from_sdl(u32 b) +{ + enum Web::UIEvents::MouseButton r = Web::UIEvents::MouseButton::None; + + if (b & SDL_BUTTON_LMASK) + r |= Web::UIEvents::MouseButton::Primary; + + if (b & SDL_BUTTON_RMASK) + r |= Web::UIEvents::MouseButton::Secondary; + + if (b & SDL_BUTTON_MMASK) + r |= Web::UIEvents::MouseButton::Middle; + + return r; +} + +static void handle_sdl_event(simple_view* v, SDL_Event const* ev) +{ + if (!v) + return; + + switch (ev->type) { + case SDL_EVENT_MOUSE_MOTION: { + Web::UIEvents::KeyModifier mods = mods_from_sdl(SDL_GetModState()); + Web::DevicePixelPoint pos = Web::DevicePixelPoint { (int)ev->motion.x, (int)ev->motion.y }; + Web::UIEvents::MouseButton btns = btns_from_sdl(ev->motion.state); + + v->enqueue_input_event(Web::MouseEvent { + Web::MouseEvent::Type::MouseMove, pos, pos, + Web::UIEvents::MouseButton::None, btns, mods, 0, 0, NULL + }); + + break; + } + + case SDL_EVENT_MOUSE_BUTTON_DOWN: { + float mx = 0.0f, my = 0.0f; + Web::UIEvents::KeyModifier mods = mods_from_sdl(SDL_GetModState()); + Web::DevicePixelPoint pos = Web::DevicePixelPoint { (int)ev->button.x, (int)ev->button.y }; + Web::UIEvents::MouseButton btn = btn_from_sdl(ev->button.button); + Web::UIEvents::MouseButton btns = btns_from_sdl(SDL_GetMouseState(&mx, &my)); + + if (btn == Web::UIEvents::MouseButton::None) + break; + + v->enqueue_input_event(Web::MouseEvent { + ev->button.clicks == 2 ? Web::MouseEvent::Type::DoubleClick : Web::MouseEvent::Type::MouseDown, + pos, pos, + btn, btns, mods, + 0, 0, NULL + }); + + break; + } + + case SDL_EVENT_MOUSE_BUTTON_UP: { + float mx = 0.0f; + float my = 0.0f; + Web::UIEvents::KeyModifier mods = mods_from_sdl(SDL_GetModState()); + Web::DevicePixelPoint pos = Web::DevicePixelPoint { (int)ev->button.x, (int)ev->button.y }; + Web::UIEvents::MouseButton btn = btn_from_sdl(ev->button.button); + Web::UIEvents::MouseButton btns = btns_from_sdl(SDL_GetMouseState(&mx, &my)); + + if (btn == Web::UIEvents::MouseButton::None) + break; + + v->enqueue_input_event(Web::MouseEvent { + Web::MouseEvent::Type::MouseUp, pos, pos, btn, btns, mods, 0, 0, NULL + }); + + if (btn == Web::UIEvents::MouseButton::Backward) + v->traverse_the_history_by_delta(-1); + else if (btn == Web::UIEvents::MouseButton::Forward) + v->traverse_the_history_by_delta(1); + + break; + } + + case SDL_EVENT_MOUSE_WHEEL: { + float mx = 0.0f; + float my = 0.0f; + Web::UIEvents::KeyModifier mods = mods_from_sdl(SDL_GetModState()); + Web::UIEvents::MouseButton btns = btns_from_sdl(SDL_GetMouseState(&mx, &my)); + Web::DevicePixelPoint pos = Web::DevicePixelPoint { (int)mx, (int)my }; + + v->enqueue_input_event(Web::MouseEvent { + Web::MouseEvent::Type::MouseWheel, pos, pos, + Web::UIEvents::MouseButton::None, btns, mods, + (int)(-ev->wheel.x * 120), (int)(-ev->wheel.y * 120), NULL + }); + + break; + } + + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: { + Web::UIEvents::KeyModifier mods = mods_from_sdl(ev->key.mod); + Web::UIEvents::KeyCode kc = key_from_sdl(ev->key.key); + + /* TEXT_INPUT generates chars so no need here */ + v->enqueue_input_event(Web::KeyEvent { + ev->type == SDL_EVENT_KEY_DOWN ? Web::KeyEvent::Type::KeyDown : Web::KeyEvent::Type::KeyUp, + kc, mods, 0, ev->key.repeat, NULL + }); + + break; + } + + case SDL_EVENT_TEXT_INPUT: { + Web::UIEvents::KeyModifier mods = mods_from_sdl(SDL_GetModState()); + for (char const* p = ev->text.text; *p; p++) { + v->enqueue_input_event(Web::KeyEvent { + Web::KeyEvent::Type::KeyDown, + Web::UIEvents::Key_Invalid, + mods, (u32)(u8)*p, false, NULL + }); + } + break; + } + + case SDL_EVENT_WINDOW_RESIZED: + v->resize(ev->window.data1, ev->window.data2); + break; + + case SDL_EVENT_WINDOW_EXPOSED: + case SDL_EVENT_WINDOW_RESTORED: + case SDL_EVENT_WINDOW_SHOWN: + v->mark_dirty(); + break; + + case SDL_EVENT_WINDOW_FOCUS_GAINED: + v->client().async_set_has_focus(v->page_idx(), true); + v->mark_dirty(); + break; + + case SDL_EVENT_WINDOW_FOCUS_LOST: + v->client().async_set_has_focus(v->page_idx(), false); + break; + } +} + +static void on_ev_tick() +{ + SDL_Event ev; + + if (!g_app || !g_app->view) + return; + + while (SDL_PollEvent(&ev)) { + if (ev.type == SDL_EVENT_QUIT) { + Core::EventLoop::current().quit(0); + return; + } + + if (ev.type == SDL_EVENT_KEY_DOWN && ev.key.key == SDLK_ESCAPE) { + Core::EventLoop::current().quit(0); + return; + } + + handle_sdl_event(g_app->view, &ev); + } +} + +static void on_paint_tick() +{ + if (!g_app || !g_app->view) + return; + + if (g_app->view->consume_dirty()) + g_app->view->paint(); +} + +ErrorOr ladybird_main(Main::Arguments args) +{ + AK::set_rich_debug_enabled(true); + + quartz* app = TRY(quartz::create(args)).leak_ptr(); + g_app = app; + + app->view = new simple_view(app->win, app->ren); + app->view->set_active(true); + + /* initial url */ + URL::URL url = URL::Parser::basic_parse("https://lite.duckduckgo.com"sv).release_value(); + app->view->load(url); + app->view->mark_dirty(); + + NonnullRefPtr ev_timer = Core::Timer::create_repeating(4, on_ev_tick); + ev_timer->start(); + + NonnullRefPtr paint_timer = Core::Timer::create_repeating(16, on_paint_tick); + paint_timer->start(); + + return Core::EventLoop::current().exec(); +} -- cgit v1.2.3