mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 14:33:47 +00:00
Merge pull request #1388 from lightpanda-io/pointer_event
add PointerEvent
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
@@ -29,6 +29,7 @@ const Page = @import("Page.zig");
|
||||
const Node = @import("webapi/Node.zig");
|
||||
const Event = @import("webapi/Event.zig");
|
||||
const UIEvent = @import("webapi/event/UIEvent.zig");
|
||||
const MouseEvent = @import("webapi/event/MouseEvent.zig");
|
||||
const Element = @import("webapi/Element.zig");
|
||||
const Document = @import("webapi/Document.zig");
|
||||
const EventTarget = @import("webapi/EventTarget.zig");
|
||||
@@ -213,6 +214,29 @@ pub fn uiEvent(self: *Factory, typ: []const u8, child: anytype) !*@TypeOf(child)
|
||||
return chain.get(2);
|
||||
}
|
||||
|
||||
pub fn mouseEvent(self: *Factory, typ: []const u8, mouse: MouseEvent, child: anytype) !*@TypeOf(child) {
|
||||
const allocator = self._slab.allocator();
|
||||
|
||||
const chain = try PrototypeChain(
|
||||
&.{ Event, UIEvent, MouseEvent, @TypeOf(child) },
|
||||
).allocate(allocator);
|
||||
|
||||
// Special case: Event has a _type_string field, so we need manual setup
|
||||
const event_ptr = chain.get(0);
|
||||
event_ptr.* = try eventInit(typ, chain.get(1), self._page);
|
||||
chain.setMiddle(1, UIEvent.Type);
|
||||
|
||||
// Set MouseEvent with all its fields
|
||||
const mouse_ptr = chain.get(2);
|
||||
mouse_ptr.* = mouse;
|
||||
mouse_ptr._proto = chain.get(1);
|
||||
mouse_ptr._type = unionInit(MouseEvent.Type, chain.get(3));
|
||||
|
||||
chain.setLeaf(3, child);
|
||||
|
||||
return chain.get(3);
|
||||
}
|
||||
|
||||
pub fn blob(self: *Factory, child: anytype) !*@TypeOf(child) {
|
||||
const allocator = self._slab.allocator();
|
||||
|
||||
|
||||
@@ -694,6 +694,7 @@ pub const JsApis = flattenTypes(&.{
|
||||
@import("../webapi/event/PopStateEvent.zig"),
|
||||
@import("../webapi/event/UIEvent.zig"),
|
||||
@import("../webapi/event/MouseEvent.zig"),
|
||||
@import("../webapi/event/PointerEvent.zig"),
|
||||
@import("../webapi/event/KeyboardEvent.zig"),
|
||||
@import("../webapi/MessageChannel.zig"),
|
||||
@import("../webapi/MessagePort.zig"),
|
||||
|
||||
165
src/browser/tests/event/pointer.html
Normal file
165
src/browser/tests/event/pointer.html
Normal file
@@ -0,0 +1,165 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
<script id=default>
|
||||
{
|
||||
let event = new PointerEvent('pointerdown');
|
||||
testing.expectEqual('pointerdown', event.type);
|
||||
testing.expectEqual(true, event instanceof PointerEvent);
|
||||
testing.expectEqual(true, event instanceof MouseEvent);
|
||||
testing.expectEqual(true, event instanceof UIEvent);
|
||||
testing.expectEqual(true, event instanceof Event);
|
||||
testing.expectEqual(0, event.pointerId);
|
||||
testing.expectEqual('', event.pointerType);
|
||||
testing.expectEqual(1.0, event.width);
|
||||
testing.expectEqual(1.0, event.height);
|
||||
testing.expectEqual(0.0, event.pressure);
|
||||
testing.expectEqual(0.0, event.tangentialPressure);
|
||||
testing.expectEqual(0, event.tiltX);
|
||||
testing.expectEqual(0, event.tiltY);
|
||||
testing.expectEqual(0, event.twist);
|
||||
testing.expectEqual(false, event.isPrimary);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=parameters>
|
||||
{
|
||||
let new_event = new PointerEvent('pointerdown', {
|
||||
pointerId: 42,
|
||||
pointerType: 'pen',
|
||||
width: 10.5,
|
||||
height: 20.5,
|
||||
pressure: 0.75,
|
||||
tangentialPressure: -0.25,
|
||||
tiltX: 30,
|
||||
tiltY: 45,
|
||||
twist: 90,
|
||||
isPrimary: true,
|
||||
clientX: 100,
|
||||
clientY: 200,
|
||||
screenX: 300,
|
||||
screenY: 400
|
||||
});
|
||||
testing.expectEqual(42, new_event.pointerId);
|
||||
testing.expectEqual('pen', new_event.pointerType);
|
||||
testing.expectEqual(10.5, new_event.width);
|
||||
testing.expectEqual(20.5, new_event.height);
|
||||
testing.expectEqual(0.75, new_event.pressure);
|
||||
testing.expectEqual(-0.25, new_event.tangentialPressure);
|
||||
testing.expectEqual(30, new_event.tiltX);
|
||||
testing.expectEqual(45, new_event.tiltY);
|
||||
testing.expectEqual(90, new_event.twist);
|
||||
testing.expectEqual(true, new_event.isPrimary);
|
||||
testing.expectEqual(100, new_event.clientX);
|
||||
testing.expectEqual(200, new_event.clientY);
|
||||
testing.expectEqual(300, new_event.screenX);
|
||||
testing.expectEqual(400, new_event.screenY);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=mousePointerType>
|
||||
{
|
||||
let mouse_event = new PointerEvent('pointerdown', { pointerType: 'mouse' });
|
||||
testing.expectEqual('mouse', mouse_event.pointerType);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=touchPointerType>
|
||||
{
|
||||
let touch_event = new PointerEvent('pointerdown', { pointerType: 'touch', pointerId: 1, pressure: 0.5 });
|
||||
testing.expectEqual('touch', touch_event.pointerType);
|
||||
testing.expectEqual(1, touch_event.pointerId);
|
||||
testing.expectEqual(0.5, touch_event.pressure);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=listener>
|
||||
{
|
||||
let pe = new PointerEvent('pointerdown', { pointerId: 123 });
|
||||
testing.expectEqual(true, pe instanceof PointerEvent);
|
||||
testing.expectEqual(true, pe instanceof MouseEvent);
|
||||
testing.expectEqual(true, pe instanceof Event);
|
||||
|
||||
var evt = null;
|
||||
document.addEventListener('pointerdown', function (e) {
|
||||
evt = e;
|
||||
});
|
||||
document.dispatchEvent(pe);
|
||||
testing.expectEqual('pointerdown', evt.type);
|
||||
testing.expectEqual(true, evt instanceof PointerEvent);
|
||||
testing.expectEqual(123, evt.pointerId);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=isTrusted>
|
||||
{
|
||||
let pointerEvent = new PointerEvent('pointerup');
|
||||
testing.expectEqual(false, pointerEvent.isTrusted);
|
||||
|
||||
let pointerIsTrusted = null;
|
||||
document.addEventListener('pointertest', (e) => {
|
||||
pointerIsTrusted = e.isTrusted;
|
||||
testing.expectEqual(true, e instanceof PointerEvent);
|
||||
});
|
||||
document.dispatchEvent(new PointerEvent('pointertest'));
|
||||
testing.expectEqual(false, pointerIsTrusted);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=eventTypes>
|
||||
{
|
||||
let down = new PointerEvent('pointerdown');
|
||||
testing.expectEqual('pointerdown', down.type);
|
||||
|
||||
let up = new PointerEvent('pointerup');
|
||||
testing.expectEqual('pointerup', up.type);
|
||||
|
||||
let move = new PointerEvent('pointermove');
|
||||
testing.expectEqual('pointermove', move.type);
|
||||
|
||||
let enter = new PointerEvent('pointerenter');
|
||||
testing.expectEqual('pointerenter', enter.type);
|
||||
|
||||
let leave = new PointerEvent('pointerleave');
|
||||
testing.expectEqual('pointerleave', leave.type);
|
||||
|
||||
let over = new PointerEvent('pointerover');
|
||||
testing.expectEqual('pointerover', over.type);
|
||||
|
||||
let out = new PointerEvent('pointerout');
|
||||
testing.expectEqual('pointerout', out.type);
|
||||
|
||||
let cancel = new PointerEvent('pointercancel');
|
||||
testing.expectEqual('pointercancel', cancel.type);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=inheritedMouseProperties>
|
||||
{
|
||||
let pe = new PointerEvent('pointerdown', {
|
||||
button: 2,
|
||||
buttons: 4,
|
||||
altKey: true,
|
||||
ctrlKey: true,
|
||||
shiftKey: true,
|
||||
metaKey: true
|
||||
});
|
||||
testing.expectEqual(2, pe.button);
|
||||
testing.expectEqual(true, pe.altKey);
|
||||
testing.expectEqual(true, pe.ctrlKey);
|
||||
testing.expectEqual(true, pe.shiftKey);
|
||||
testing.expectEqual(true, pe.metaKey);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=inheritedUIEventProperties>
|
||||
{
|
||||
let pe = new PointerEvent('pointerdown', {
|
||||
detail: 5,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
testing.expectEqual(5, pe.detail);
|
||||
testing.expectEqual(true, pe.bubbles);
|
||||
testing.expectEqual(true, pe.cancelable);
|
||||
}
|
||||
</script>
|
||||
@@ -22,10 +22,11 @@ const UIEvent = @import("UIEvent.zig");
|
||||
const EventTarget = @import("../EventTarget.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const js = @import("../../js/js.zig");
|
||||
const PointerEvent = @import("PointerEvent.zig");
|
||||
|
||||
const MouseEvent = @This();
|
||||
|
||||
const MouseButton = enum(u8) {
|
||||
pub const MouseButton = enum(u8) {
|
||||
main = 0,
|
||||
auxillary = 1,
|
||||
secondary = 2,
|
||||
@@ -33,6 +34,12 @@ const MouseButton = enum(u8) {
|
||||
fifth = 4,
|
||||
};
|
||||
|
||||
pub const Type = union(enum) {
|
||||
generic,
|
||||
pointer_event: *PointerEvent,
|
||||
};
|
||||
|
||||
_type: Type,
|
||||
_proto: *UIEvent,
|
||||
|
||||
_alt_key: bool,
|
||||
@@ -62,7 +69,7 @@ pub const MouseEventOptions = struct {
|
||||
relatedTarget: ?*EventTarget = null,
|
||||
};
|
||||
|
||||
const Options = Event.inheritOptions(
|
||||
pub const Options = Event.inheritOptions(
|
||||
MouseEvent,
|
||||
MouseEventOptions,
|
||||
);
|
||||
@@ -73,6 +80,7 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*MouseEvent {
|
||||
const event = try page._factory.uiEvent(
|
||||
typ,
|
||||
MouseEvent{
|
||||
._type = .generic,
|
||||
._proto = undefined,
|
||||
._screen_x = opts.screenX,
|
||||
._screen_y = opts.screenY,
|
||||
@@ -95,6 +103,18 @@ pub fn asEvent(self: *MouseEvent) *Event {
|
||||
return self._proto.asEvent();
|
||||
}
|
||||
|
||||
pub fn as(self: *MouseEvent, comptime T: type) *T {
|
||||
return self.is(T).?;
|
||||
}
|
||||
|
||||
pub fn is(self: *MouseEvent, comptime T: type) ?*T {
|
||||
switch (self._type) {
|
||||
.generic => return if (T == MouseEvent) self else null,
|
||||
.pointer_event => |e| return if (T == PointerEvent) e else null,
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getAltKey(self: *const MouseEvent) bool {
|
||||
return self._alt_key;
|
||||
}
|
||||
|
||||
202
src/browser/webapi/event/PointerEvent.zig
Normal file
202
src/browser/webapi/event/PointerEvent.zig
Normal file
@@ -0,0 +1,202 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program 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 Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Event = @import("../Event.zig");
|
||||
const MouseEvent = @import("MouseEvent.zig");
|
||||
|
||||
const PointerEvent = @This();
|
||||
|
||||
const PointerType = enum {
|
||||
empty,
|
||||
mouse,
|
||||
pen,
|
||||
touch,
|
||||
|
||||
fn fromString(s: []const u8) PointerType {
|
||||
if (std.mem.eql(u8, s, "")) return .empty;
|
||||
if (std.mem.eql(u8, s, "mouse")) return .mouse;
|
||||
if (std.mem.eql(u8, s, "pen")) return .pen;
|
||||
if (std.mem.eql(u8, s, "touch")) return .touch;
|
||||
return .empty;
|
||||
}
|
||||
|
||||
fn toString(self: PointerType) []const u8 {
|
||||
return switch (self) {
|
||||
.empty => "",
|
||||
inline else => |pt| @tagName(pt),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
_proto: *MouseEvent,
|
||||
_pointer_id: i32,
|
||||
_pointer_type: PointerType,
|
||||
_width: f64,
|
||||
_height: f64,
|
||||
_pressure: f64,
|
||||
_tangential_pressure: f64,
|
||||
_tilt_x: i32,
|
||||
_tilt_y: i32,
|
||||
_twist: i32,
|
||||
_altitude_angle: f64,
|
||||
_azimuth_angle: f64,
|
||||
_is_primary: bool,
|
||||
|
||||
pub const PointerEventOptions = struct {
|
||||
pointerId: i32 = 0,
|
||||
pointerType: []const u8 = "",
|
||||
width: f64 = 1.0,
|
||||
height: f64 = 1.0,
|
||||
pressure: f64 = 0.0,
|
||||
tangentialPressure: f64 = 0.0,
|
||||
tiltX: i32 = 0,
|
||||
tiltY: i32 = 0,
|
||||
twist: i32 = 0,
|
||||
altitudeAngle: f64 = std.math.pi / 2.0,
|
||||
azimuthAngle: f64 = 0.0,
|
||||
isPrimary: bool = false,
|
||||
};
|
||||
|
||||
const Options = Event.inheritOptions(
|
||||
PointerEvent,
|
||||
PointerEventOptions,
|
||||
);
|
||||
|
||||
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*PointerEvent {
|
||||
const opts = _opts orelse Options{};
|
||||
|
||||
const event = try page._factory.mouseEvent(
|
||||
typ,
|
||||
MouseEvent{
|
||||
._type = .{ .pointer_event = undefined },
|
||||
._proto = undefined,
|
||||
._screen_x = opts.screenX,
|
||||
._screen_y = opts.screenY,
|
||||
._client_x = opts.clientX,
|
||||
._client_y = opts.clientY,
|
||||
._ctrl_key = opts.ctrlKey,
|
||||
._shift_key = opts.shiftKey,
|
||||
._alt_key = opts.altKey,
|
||||
._meta_key = opts.metaKey,
|
||||
._button = std.meta.intToEnum(MouseEvent.MouseButton, opts.button) catch return error.TypeError,
|
||||
._related_target = opts.relatedTarget,
|
||||
},
|
||||
PointerEvent{
|
||||
._proto = undefined,
|
||||
._pointer_id = opts.pointerId,
|
||||
._pointer_type = PointerType.fromString(opts.pointerType),
|
||||
._width = opts.width,
|
||||
._height = opts.height,
|
||||
._pressure = opts.pressure,
|
||||
._tangential_pressure = opts.tangentialPressure,
|
||||
._tilt_x = opts.tiltX,
|
||||
._tilt_y = opts.tiltY,
|
||||
._twist = opts.twist,
|
||||
._altitude_angle = opts.altitudeAngle,
|
||||
._azimuth_angle = opts.azimuthAngle,
|
||||
._is_primary = opts.isPrimary,
|
||||
},
|
||||
);
|
||||
|
||||
Event.populatePrototypes(event, opts, false);
|
||||
return event;
|
||||
}
|
||||
|
||||
pub fn asEvent(self: *PointerEvent) *Event {
|
||||
return self._proto.asEvent();
|
||||
}
|
||||
|
||||
pub fn getPointerId(self: *const PointerEvent) i32 {
|
||||
return self._pointer_id;
|
||||
}
|
||||
|
||||
pub fn getPointerType(self: *const PointerEvent) []const u8 {
|
||||
return self._pointer_type.toString();
|
||||
}
|
||||
|
||||
pub fn getWidth(self: *const PointerEvent) f64 {
|
||||
return self._width;
|
||||
}
|
||||
|
||||
pub fn getHeight(self: *const PointerEvent) f64 {
|
||||
return self._height;
|
||||
}
|
||||
|
||||
pub fn getPressure(self: *const PointerEvent) f64 {
|
||||
return self._pressure;
|
||||
}
|
||||
|
||||
pub fn getTangentialPressure(self: *const PointerEvent) f64 {
|
||||
return self._tangential_pressure;
|
||||
}
|
||||
|
||||
pub fn getTiltX(self: *const PointerEvent) i32 {
|
||||
return self._tilt_x;
|
||||
}
|
||||
|
||||
pub fn getTiltY(self: *const PointerEvent) i32 {
|
||||
return self._tilt_y;
|
||||
}
|
||||
|
||||
pub fn getTwist(self: *const PointerEvent) i32 {
|
||||
return self._twist;
|
||||
}
|
||||
|
||||
pub fn getAltitudeAngle(self: *const PointerEvent) f64 {
|
||||
return self._altitude_angle;
|
||||
}
|
||||
|
||||
pub fn getAzimuthAngle(self: *const PointerEvent) f64 {
|
||||
return self._azimuth_angle;
|
||||
}
|
||||
|
||||
pub fn getIsPrimary(self: *const PointerEvent) bool {
|
||||
return self._is_primary;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(PointerEvent);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "PointerEvent";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(PointerEvent.init, .{});
|
||||
pub const pointerId = bridge.accessor(PointerEvent.getPointerId, null, .{});
|
||||
pub const pointerType = bridge.accessor(PointerEvent.getPointerType, null, .{});
|
||||
pub const width = bridge.accessor(PointerEvent.getWidth, null, .{});
|
||||
pub const height = bridge.accessor(PointerEvent.getHeight, null, .{});
|
||||
pub const pressure = bridge.accessor(PointerEvent.getPressure, null, .{});
|
||||
pub const tangentialPressure = bridge.accessor(PointerEvent.getTangentialPressure, null, .{});
|
||||
pub const tiltX = bridge.accessor(PointerEvent.getTiltX, null, .{});
|
||||
pub const tiltY = bridge.accessor(PointerEvent.getTiltY, null, .{});
|
||||
pub const twist = bridge.accessor(PointerEvent.getTwist, null, .{});
|
||||
pub const altitudeAngle = bridge.accessor(PointerEvent.getAltitudeAngle, null, .{});
|
||||
pub const azimuthAngle = bridge.accessor(PointerEvent.getAzimuthAngle, null, .{});
|
||||
pub const isPrimary = bridge.accessor(PointerEvent.getIsPrimary, null, .{});
|
||||
};
|
||||
|
||||
const testing = @import("../../../testing.zig");
|
||||
test "WebApi: PointerEvent" {
|
||||
try testing.htmlRunner("event/pointer.html", .{});
|
||||
}
|
||||
@@ -68,7 +68,10 @@ pub fn as(self: *UIEvent, comptime T: type) *T {
|
||||
pub fn is(self: *UIEvent, comptime T: type) ?*T {
|
||||
switch (self._type) {
|
||||
.generic => return if (T == UIEvent) self else null,
|
||||
.mouse_event => |e| return if (T == @import("MouseEvent.zig")) e else null,
|
||||
.mouse_event => |e| {
|
||||
if (T == @import("MouseEvent.zig")) return e;
|
||||
return e.is(T);
|
||||
},
|
||||
.keyboard_event => |e| return if (T == @import("KeyboardEvent.zig")) e else null,
|
||||
}
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user