diff --git a/src/libs/karm-app/inputs.h b/src/libs/karm-app/inputs.h index 5ffbdf011a..4f1777ab2f 100644 --- a/src/libs/karm-app/inputs.h +++ b/src/libs/karm-app/inputs.h @@ -125,6 +125,14 @@ struct MouseEvent { MouseButton buttons{}; KeyMod mods{}; MouseButton button{}; + + bool pressed(MouseButton button) const { + return (bool)(buttons & button); + } + + bool released(MouseButton button) const { + return not pressed(button); + } }; struct MouseLeaveEvent { diff --git a/src/libs/karm-gfx/cpu/surface.h b/src/libs/karm-gfx/cpu/surface.h new file mode 100644 index 0000000000..a4f2853b8f --- /dev/null +++ b/src/libs/karm-gfx/cpu/surface.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../buffer.h" + +namespace Karm::Gfx { + +struct CpuSurface { + virtual ~CpuSurface() = default; + + virtual Math::Recti bound() { + return mutPixels().bound(); + } + + virtual MutPixels mutPixels() = 0; +}; + +} // namespace Karm::Gfx diff --git a/src/libs/karm-sys/rpc.h b/src/libs/karm-sys/rpc.h index 9dfa650108..e569de5f4e 100644 --- a/src/libs/karm-sys/rpc.h +++ b/src/libs/karm-sys/rpc.h @@ -186,7 +186,7 @@ struct Rpc : Meta::Pinned { Rpc(Sys::IpcConnection con) : _con(std::move(con)) { - // FIXME: Fid a way to do proper cleanup + // FIXME: Find a way to do proper cleanup Async::detach(_receiverTask(*this), [](Res<> res) { logError("receiver task exited: {}", res); panic("receiver task exited"); diff --git a/src/srvs/grund-device/ps2.cpp b/src/srvs/grund-device/ps2.cpp index ffa46901f6..b1522edeb7 100644 --- a/src/srvs/grund-device/ps2.cpp +++ b/src/srvs/grund-device/ps2.cpp @@ -182,11 +182,17 @@ Res<> Mouse::decode() { if (_buf[0] & 0x20) offy -= 0x100; + App::MouseButton buttons = App::MouseButton::NONE; + + buttons |= ((_buf[0] >> 0) & 1) ? App::MouseButton::LEFT : App::MouseButton::NONE; + buttons |= ((_buf[0] >> 1) & 1) ? App::MouseButton::RIGHT : App::MouseButton::NONE; + buttons |= ((_buf[0] >> 2) & 1) ? App::MouseButton::MIDDLE : App::MouseButton::NONE; + int scroll = 0; if (_hasWheel) scroll = (i8)_buf[3]; - auto event = App::makeEvent(App::MouseEvent::MOVE, 0, scroll, Math::Vec2i{offx, -offy}); + auto event = App::makeEvent(App::MouseEvent::MOVE, 0, scroll, Math::Vec2i{offx, -offy}, buttons); logInfo("ps2: mouse move {} {} {}", offx, offy, scroll); try$(bubble(*event)); diff --git a/src/srvs/grund-shell/host.cpp b/src/srvs/grund-shell/host.cpp deleted file mode 100644 index 417d16d2bd..0000000000 --- a/src/srvs/grund-shell/host.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include -#include - -#include "host.h" - -namespace Grund::Shell { - -Res openFramebuffer(Sys::Context &ctx) { - auto &handover = useHandover(ctx); - - auto *fb = handover.findTag(Handover::Tag::FB); - auto fbVmo = try$(Hj::Vmo::create(Hj::ROOT, fb->start, fb->size, Hj::VmoFlags::DMA)); - try$(fbVmo.label("framebuffer")); - - static auto fbRange = try$(Hj::map(fbVmo, Hj::MapFlags::READ | Hj::MapFlags::WRITE)); - - logInfo("fb: {x}-{x} {}x{}, {} stride", fbRange.range().start, fbRange.range().end(), fb->fb.width, fb->fb.height, fb->fb.pitch); - - return Ok(Gfx::MutPixels{ - fbRange.mutBytes().buf(), - {fb->fb.width, fb->fb.height}, - fb->fb.pitch, - Gfx::BGRA8888, - }); -} - -Res> makeHost(Sys::Context &ctx, Ui::Child root) { - Gfx::MutPixels front = try$(openFramebuffer(ctx)); - auto back = Gfx::Surface::alloc( - front.size(), Gfx::BGRA8888 - ); - - return Ok(makeStrong(std::move(root), front, back)); -} - -} // namespace Grund::Shell diff --git a/src/srvs/grund-shell/host.h b/src/srvs/grund-shell/host.h deleted file mode 100644 index 9238ac7586..0000000000 --- a/src/srvs/grund-shell/host.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace Grund::Shell { - -struct RootHost : - public Ui::Host { - Gfx::MutPixels _front; - Strong _back; - - RootHost(Ui::Child root, Gfx::MutPixels front, Strong back) - : Ui::Host(root), - _front(front), - _back(back) { - } - - Gfx::MutPixels mutPixels() override { - return _back->mutPixels(); - } - - void flip(Slice dirty) override { - for (auto d : dirty) - Gfx::blitUnsafe(_front.clip(d), _back->pixels().clip(d)); - } - - Res<> wait(TimeStamp until) override { - try$(Sys::sleepUntil(until)); - return Ok(); - } - - void bubble(App::Event &e) override { - Ui::Host::bubble(e); - } -}; - -Res openFramebuffer(Sys::Context &ctx); - -Res> makeHost(Sys::Context &ctx, Ui::Child root); - -} // namespace Grund::Shell diff --git a/src/srvs/grund-shell/main.cpp b/src/srvs/grund-shell/main.cpp index ceb4451f60..c07cc8664c 100644 --- a/src/srvs/grund-shell/main.cpp +++ b/src/srvs/grund-shell/main.cpp @@ -1,7 +1,14 @@ +#include #include +#include +#include +#include +#include #include #include #include +#include +#include #include #include #include @@ -15,35 +22,236 @@ #include #include -#include "host.h" - namespace Grund::Shell { -Async::Task<> servAsync(Sys::Context &ctx) { - auto rpc = Sys::Rpc::create(ctx); - auto fontbuffer = co_try$(openFramebuffer(ctx)); +struct Framebuffer : public Gfx::CpuSurface { + static Res> open(Sys::Context &ctx) { + auto &handover = useHandover(ctx); + auto *fb = handover.findTag(Handover::Tag::FB); + auto fbVmo = try$(Hj::Vmo::create(Hj::ROOT, fb->start, fb->size, Hj::VmoFlags::DMA)); + try$(fbVmo.label("framebuffer")); + return Ok(makeStrong(*fb, try$(Hj::map(fbVmo, Hj::MapFlags::READ | Hj::MapFlags::WRITE)))); + } - auto backbuffer = Gfx::Surface::alloc(fontbuffer.size(), Gfx::BGRA8888); - Gfx::CpuCanvas g; + Handover::Record _record; + Hj::Mapped _map; - Math::Vec2i mouspos = fontbuffer.size() / 2; + Framebuffer(Handover::Record record, Hj::Mapped map) + : _record(record), _map(std::move(map)) { + logInfo( + "fb: {x}-{x} {}x{}, {} stride", + _map.range().start, + _map.range().end(), + _record.fb.width, + _record.fb.height, + _record.fb.pitch + ); + } - while (true) { - g.begin(*backbuffer); - g.clear(Ui::GRAY950); - g.beginPath(); - g.fillStyle(Gfx::WHITE.withOpacity(0.25)); - g.ellipse(Math::Ellipsef{mouspos.cast(), {16, 16}}); - g.fill(Gfx::FillRule::EVENODD); + Gfx::MutPixels mutPixels() { + return { + _map.mutBytes().buf(), + { + _record.fb.width, + _record.fb.height, + }, + _record.fb.pitch, + Gfx::BGRA8888, + }; + } +}; + +struct InputTranslator : public Ui::ProxyNode { + App::MouseEvent _mousePrev = {}; + Math::Vec2i _mousePos = {}; + + InputTranslator(Ui::Child child) + : Ui::ProxyNode(std::move(child)) { + } + + Math::Ellipsef _cursor() const { + return { + _mousePos.cast(), + {16, 16}, + }; + } + + Math::Recti _cursorDamage() const { + return _cursor().bound().grow(1).cast(); + } + + void event(App::Event &e) override { + if (auto m = e.is()) { + Ui::shouldRepaint(*this, _cursorDamage()); + _mousePos = _mousePos + m->delta; + Ui::shouldRepaint(*this, _cursorDamage()); + e.accept(); + + if (m->delta != Math::Vec2i{}) { + App::MouseEvent mouseMove = *m; + mouseMove.type = App::MouseEvent::MOVE; + mouseMove.pos = _mousePos; + Ui::event(child(), mouseMove); + } + + if (_mousePrev.released(App::MouseButton::LEFT) and + m->pressed(App::MouseButton::LEFT)) { + App::MouseEvent mousePress = *m; + mousePress.type = App::MouseEvent::PRESS; + mousePress.pos = _mousePos; + mousePress.button = App::MouseButton::LEFT; + Ui::event(child(), mousePress); + } + + if (_mousePrev.pressed(App::MouseButton::LEFT) and + m->released(App::MouseButton::LEFT)) { + App::MouseEvent mouseRelease = *m; + mouseRelease.type = App::MouseEvent::RELEASE; + mouseRelease.pos = _mousePos; + mouseRelease.button = App::MouseButton::LEFT; + Ui::event(child(), mouseRelease); + } + + _mousePrev = *m; + } + + Ui::ProxyNode::event(e); + } + + void paint(Gfx::Canvas &g, Math::Recti r) override { + child().paint(g, r); + + if (_cursorDamage().colide(r)) { + g.push(); + g.beginPath(); + g.fillStyle(Gfx::WHITE.withOpacity(0.25)); + g.ellipse(_cursor()); + g.fill(Gfx::FillRule::EVENODD); + g.pop(); + } + } +}; + +Ui::Child inputTranslator(Ui::Child child) { + return makeStrong(std::move(child)); +} + +struct Root : public Ui::ProxyNode { + Vec _dirty; + Strong _frontbuffer; + Strong _backbuffer; + bool _shouldLayout{}; + + Root(Ui::Child child, Strong frontbuffer) + : Ui::ProxyNode(std::move(child)), + _frontbuffer(std::move(frontbuffer)), + _backbuffer(Gfx::Surface::alloc(_frontbuffer->bound().size(), Gfx::BGRA8888)) { + } + + void _repaint() { + Gfx::CpuCanvas g; + g.begin(*_backbuffer); + for (auto &r : _dirty) { + g.push(); + g.clear(r, Ui::GRAY950); + g.fillStyle(Ui::GRAY50); + g.clip(r.cast()); + paint(g, r); + g.pop(); + + Gfx::blitUnsafe(_frontbuffer->mutPixels(), _backbuffer->pixels()); + } g.end(); - Gfx::blitUnsafe(fontbuffer, *backbuffer); + _dirty.clear(); + } + + Async::Task<> run() { + _shouldLayout = true; + _dirty.pushBack(_backbuffer->bound()); + + while (true) { + auto e = App::makeEvent(Ui::FRAME_TIME); + event(*e); + + if (_shouldLayout) { + layout(bound()); + _shouldLayout = false; + } + + if (_dirty.len() > 0) { + _repaint(); + } + + co_trya$(Sys::globalSched().sleepAsync(Sys::now() + 16_ms)); + } + } + + Math::Recti bound() override { + return _backbuffer->bound(); + } + + void bubble(App::Event &event) override { + if (auto e = event.is()) { + _dirty.pushBack(e->bound); + event.accept(); + } else if (auto e = event.is()) { + _shouldLayout = true; + event.accept(); + } else if (auto e = event.is()) { + event.accept(); + } else if (auto e = event.is()) { + event.accept(); + } + + if (not event.accepted()) { + logWarn("unhandled event, bouncing down"); + this->event(event); + } + } +}; + +Async::Task<> servAsync(Sys::Context &ctx) { + + Hideo::Shell::State state = { + .isMobile = false, + .dateTime = Sys::dateTime(), + .background = co_try$(Image::loadOrFallback("bundle://hideo-shell/wallpaper.qoi"_url)), + .noti = {}, + .manifests = { + makeStrong(Mdi::INFORMATION_OUTLINE, "About"s, Gfx::BLUE_RAMP), + makeStrong(Mdi::CALCULATOR, "Calculator"s, Gfx::ORANGE_RAMP), + makeStrong(Mdi::PALETTE_SWATCH, "Color Picker"s, Gfx::RED_RAMP), + makeStrong(Mdi::COUNTER, "Counter"s, Gfx::GREEN_RAMP), + makeStrong(Mdi::DUCK, "Demos"s, Gfx::YELLOW_RAMP), + makeStrong(Mdi::FILE, "Files"s, Gfx::ORANGE_RAMP), + makeStrong(Mdi::FORMAT_FONT, "Fonts"s, Gfx::BLUE_RAMP), + makeStrong(Mdi::EMOTICON, "Hello World"s, Gfx::RED_RAMP), + makeStrong(Mdi::IMAGE, "Icons"s, Gfx::GREEN_RAMP), + makeStrong(Mdi::IMAGE, "Image Viewer"s, Gfx::YELLOW_RAMP), + makeStrong(Mdi::COG, "Settings"s, Gfx::ZINC_RAMP), + makeStrong(Mdi::TABLE, "Spreadsheet"s, Gfx::GREEN_RAMP), + makeStrong(Mdi::WIDGETS, "Widget Gallery"s, Gfx::BLUE_RAMP), + }, + .instances = {} + }; + + auto rpc = Sys::Rpc::create(ctx); + auto root = makeStrong( + Hideo::Shell::app(std::move(state)) | inputTranslator, + co_try$(Framebuffer::open(ctx)) + ); + Async::detach(root->run()); + + while (true) { auto msg = co_trya$(rpc.recvAsync()); if (msg.is()) { - auto event = msg.unpack().unwrap(); - mouspos = mouspos + event.delta; + auto rawEvent = msg.unpack().unwrap(); + auto event = App::makeEvent(rawEvent); + root->child().event(*event); + logDebug("mouse event!"); } else if (msg.is()) { auto event = msg.unpack(); @@ -58,30 +266,4 @@ Async::Task<> servAsync(Sys::Context &ctx) { Async::Task<> entryPointAsync(Sys::Context &ctx) { return Grund::Shell::servAsync(ctx); - // Hideo::Shell::State state = { - // .isMobile = false, - // .dateTime = Sys::dateTime(), - // .background = co_try$(Image::loadOrFallback("bundle://hideo-shell/wallpaper.qoi"_url)), - // .noti = {}, - // .manifests = { - // makeStrong(Mdi::INFORMATION_OUTLINE, "About"s, Gfx::BLUE_RAMP), - // makeStrong(Mdi::CALCULATOR, "Calculator"s, Gfx::ORANGE_RAMP), - // makeStrong(Mdi::PALETTE_SWATCH, "Color Picker"s, Gfx::RED_RAMP), - // makeStrong(Mdi::COUNTER, "Counter"s, Gfx::GREEN_RAMP), - // makeStrong(Mdi::DUCK, "Demos"s, Gfx::YELLOW_RAMP), - // makeStrong(Mdi::FILE, "Files"s, Gfx::ORANGE_RAMP), - // makeStrong(Mdi::FORMAT_FONT, "Fonts"s, Gfx::BLUE_RAMP), - // makeStrong(Mdi::EMOTICON, "Hello World"s, Gfx::RED_RAMP), - // makeStrong(Mdi::IMAGE, "Icons"s, Gfx::GREEN_RAMP), - // makeStrong(Mdi::IMAGE, "Image Viewer"s, Gfx::YELLOW_RAMP), - // makeStrong(Mdi::COG, "Settings"s, Gfx::ZINC_RAMP), - // makeStrong(Mdi::TABLE, "Spreadsheet"s, Gfx::GREEN_RAMP), - // makeStrong(Mdi::WIDGETS, "Widget Gallery"s, Gfx::BLUE_RAMP), - // }, - // .instances = {} - // }; - // - // auto app = Hideo::Shell::app(std::move(state)); - // auto host = co_try$(Grund::Shell::makeHost(ctx, app)); - // co_return host->run(); }