SDL3_API分类参考_手写笔(CategoryPen)

2026-3-6 / 0 评论 / 4 阅读

手写笔子系统(CategoryPen)

SDL 提供的压感手写笔(触控笔/橡皮擦)事件处理模块,适用于绘图板、配备手写笔的移动设备/平板等场景的精准输入处理。

核心事件类型

要处理手写笔输入,只需监听以下核心事件:

  • SDL_EVENT_PEN_PROXIMITY_IN/SDL_EVENT_PEN_PROXIMITY_OUT:手写笔进入/离开感应范围事件(SDL_PenProximityEvent);
  • SDL_EVENT_PEN_DOWN/SDL_EVENT_PEN_UP:手写笔按下/抬起事件(SDL_PenTouchEvent);
  • SDL_EVENT_PEN_MOTION:手写笔移动事件(SDL_PenMotionEvent);
  • SDL_EVENT_PEN_BUTTON_DOWN/SDL_EVENT_PEN_BUTTON_UP:手写笔按键按下/抬起事件(SDL_PenButtonEvent);
  • SDL_EVENT_PEN_AXIS:手写笔轴数据变化事件(SDL_PenAxisEvent)。

手写笔高级特性

手写笔不仅支持基础的触摸输入,还可提供压力、倾斜角度、旋转角度等额外轴数据,满足精细绘图、书写等场景需求。

手写笔ID规则

  • 手写笔开始输入时,SDL 会分配唯一的 SDL_PenID,只要设备保持连接,该ID在进程生命周期内保持不变;
  • 手写笔离开感应范围(如远离数位板)后重新进入,会触发接近事件,但 SDL_PenID 仍保持一致;
  • 数位板拔插后重新连接,SDL 可能无法识别为同一硬件,新输入会分配新的 SDL_PenID

跨平台注意事项

不同平台对手写笔的支持程度差异极大:

  • 部分平台支持多设备同时工作,部分平台会将所有连接的手写笔视为单个逻辑设备(类似多USB鼠标控制同一系统光标);
  • 部分平台不支持手写笔按键、距离轴等功能;
  • 极少平台能提前报告手写笔支持的功能,最佳实践是:要么提供UI让用户配置手写笔,要么在首次收到事件时动态适配新功能;
  • 若手写笔硬件支持某功能但SDL未响应,大概率是操作系统层面的支持问题。

函数

  • SDL_GetPenDeviceType:获取手写笔关联设备的类型(如数位板、触摸屏、平板等)

数据类型

  • SDL_PenID:手写笔唯一标识类型(区分不同手写笔设备/实例)
  • SDL_PenInputFlags:手写笔输入标志类型(位掩码,标识输入特性:如橡皮擦模式、桶按钮按下等)

结构体

  • SDL_PenAxisEvent:手写笔轴事件结构体(包含轴类型、轴数值、手写笔ID等)
  • SDL_PenButtonEvent:手写笔按键事件结构体(包含按键ID、按下/抬起状态、手写笔ID等)
  • SDL_PenMotionEvent:手写笔移动事件结构体(包含位置、压力、倾斜角度、手写笔ID等)
  • SDL_PenProximityEvent:手写笔接近事件结构体(包含进入/离开状态、手写笔ID、设备类型等)
  • SDL_PenTouchEvent:手写笔触摸事件结构体(包含按下/抬起状态、位置、压力、手写笔ID等)

枚举

  • SDL_PenAxis:手写笔轴类型枚举(如压力轴、X/Y倾斜轴、旋转轴、距离轴等)
  • SDL_PenDeviceType:手写笔关联设备类型枚举(如数位板、触摸屏、平板、未知设备等)

  • SDL_PEN_MOUSEID:手写笔模拟鼠标事件的设备ID(用于区分原生鼠标和手写笔模拟鼠标)
  • SDL_PEN_TOUCHID:手写笔关联的触摸设备ID标识(用于关联手写笔事件到触摸设备)

FreeBASIC 示例代码

' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库)
#Include "SDL.bi"

' 绘图状态结构体
Type DrawingState
    isDrawing As Boolean          ' 是否正在绘图
    lastX As Integer              ' 上一帧X坐标
    lastY As Integer              ' 上一帧Y坐标
    penID As SDL_PenID            ' 当前手写笔ID
    pressure As Single            ' 压感值(0.0~1.0)
    tiltX As Single              ' X轴倾斜角度(-1.0~1.0)
    tiltY As Single              ' Y轴倾斜角度(-1.0~1.0)
End Type

Dim Shared As DrawingState drawState

' 初始化绘图状态
Sub InitDrawingState()
    drawState.isDrawing = False
    drawState.lastX = 0
    drawState.lastY = 0
    drawState.penID = 0
    drawState.pressure = 0.0
    drawState.tiltX = 0.0
    drawState.tiltY = 0.0
End Sub

' 绘制手写笔轨迹
Sub DrawPenStroke(ByVal renderer As SDL_Renderer Ptr, ByVal x As Integer, ByVal y As Integer)
    If (Not drawState.isDrawing) Then Return

    ' 根据压感值调整线条宽度(1~20像素)
    Dim As Integer lineWidth = 1 + Int(drawState.pressure * 19)

    ' 根据倾斜角度调整颜色(倾斜越大,颜色越浅)
    Dim As UByte alpha = 255 - Int((Abs(drawState.tiltX) + Abs(drawState.tiltY)) * 128)
    SDL_SetRenderDrawColor(renderer, 0, 150, 255, alpha)

    ' 设置线条宽度(SDL3 支持渲染器线条宽度)
    SDL_SetRenderDrawLineWidth(renderer, lineWidth)

    ' 绘制线条(从上一位置到当前位置)
    SDL_RenderDrawLine(renderer, drawState.lastX, drawState.lastY, x, y)

    ' 更新上一位置
    drawState.lastX = x
    drawState.lastY = y
End Sub

' 打印手写笔事件信息
Sub PrintPenEventInfo(ByRef evt As SDL_Event)
    Select Case evt.type
        Case SDL_EVENT_PEN_PROXIMITY_IN
            SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "手写笔进入感应范围 | PenID:%llu | 设备类型:%d", _
                evt.penproximity.penid, evt.penproximity.devicetype)
        Case SDL_EVENT_PEN_PROXIMITY_OUT
            SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "手写笔离开感应范围 | PenID:%llu", evt.penproximity.penid)
        Case SDL_EVENT_PEN_DOWN
            SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _
                "手写笔按下 | PenID:%llu | 位置:(%d, %d) | 压力:%.2f", _
                evt.pentouch.penid, evt.pentouch.x, evt.pentouch.y, evt.pentouch.pressure)
        Case SDL_EVENT_PEN_UP
            SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _
                "手写笔抬起 | PenID:%llu | 位置:(%d, %d)", _
                evt.pentouch.penid, evt.pentouch.x, evt.pentouch.y)
        Case SDL_EVENT_PEN_MOTION
            SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _
                "手写笔移动 | PenID:%llu | 位置:(%d, %d) | 压力:%.2f | 倾斜(X/Y):(%.2f, %.2f)", _
                evt.penmotion.penid, evt.penmotion.x, evt.penmotion.y, _
                evt.penmotion.pressure, evt.penmotion.tiltx, evt.penmotion.tilty)
        Case SDL_EVENT_PEN_BUTTON_DOWN
            SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _
                "手写笔按键按下 | PenID:%llu | 按键ID:%d", _
                evt.penbutton.penid, evt.penbutton.button)
        Case SDL_EVENT_PEN_BUTTON_UP
            SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _
                "手写笔按键抬起 | PenID:%llu | 按键ID:%d", _
                evt.penbutton.penid, evt.penbutton.button)
        Case SDL_EVENT_PEN_AXIS
            Dim As ZString Ptr axisName = SDL_GetStringForPenAxis(evt.penaxis.axis)
            SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _
                "手写笔轴变化 | PenID:%llu | 轴类型:%s | 数值:%.2f", _
                evt.penaxis.penid, axisName, evt.penaxis.value)
            If (axisName <> NULL) Then SDL_free(axisName)
    End Select
End Sub

' 主程序
Dim As SDL_Window Ptr window = NULL
Dim As SDL_Renderer Ptr renderer = NULL
Dim As SDL_Event evt
Dim As Boolean quit = False

' 1. 初始化 SDL
If (SDL_Init(SDL_INIT_VIDEO) < 0) Then
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL 初始化失败:%s", SDL_GetError())
    End 1
End If

' 2. 创建绘图窗口(适配手写板/平板的高分辨率)
window = SDL_CreateWindow("SDL 手写笔绘图示例", _
    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, _
    1280, 720, SDL_WINDOW_SHOWN Or SDL_WINDOW_RESIZABLE)
renderer = SDL_CreateRenderer(window, -1, _
    SDL_RENDERER_ACCELERATED Or SDL_RENDERER_PRESENTVSYNC Or SDL_RENDERER_TARGETTEXTURE)

If (window = NULL Or renderer = NULL) Then
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "创建窗口/渲染器失败:%s", SDL_GetError())
    SDL_Quit()
    End 1
End If

' 初始化绘图状态
InitDrawingState()

SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "=== SDL 手写笔绘图示例 ===")
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "手写笔操作:绘图(压感/倾斜生效) | 按键:测试笔侧键 | ESC:退出")
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "提示:若无手写笔硬件,可测试鼠标模拟输入(区分原生/手写笔模拟)")

' 3. 主循环
While (Not quit)
    ' 清屏(白色背景,模拟画纸)
    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255)
    SDL_RenderClear(renderer)

    ' 处理事件队列
    While (SDL_PollEvent(@evt))
        Select Case evt.type
            Case SDL_QUITEVENT
                quit = True

            ' 键盘事件
            Case SDL_EVENT_KEY_DOWN
                If (evt.key.scancode = SDL_SCANCODE_ESCAPE) Then
                    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "按下 ESC,退出程序")
                    quit = True
                ElseIf (evt.key.scancode = SDL_SCANCODE_C) Then
                    ' 按C键清空画布
                    InitDrawingState()
                    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "清空画布")
                End If

            ' 手写笔核心事件
            Case SDL_EVENT_PEN_PROXIMITY_IN, SDL_EVENT_PEN_PROXIMITY_OUT, _
                 SDL_EVENT_PEN_DOWN, SDL_EVENT_PEN_UP, SDL_EVENT_PEN_MOTION, _
                 SDL_EVENT_PEN_BUTTON_DOWN, SDL_EVENT_PEN_BUTTON_UP, SDL_EVENT_PEN_AXIS
                ' 打印事件信息
                PrintPenEventInfo(evt)

                ' 处理绘图逻辑
                Select Case evt.type
                    Case SDL_EVENT_PEN_DOWN
                        drawState.isDrawing = True
                        drawState.penID = evt.pentouch.penid
                        drawState.lastX = evt.pentouch.x
                        drawState.lastY = evt.pentouch.y
                        drawState.pressure = evt.pentouch.pressure
                    Case SDL_EVENT_PEN_UP
                        drawState.isDrawing = False
                    Case SDL_EVENT_PEN_MOTION
                        drawState.pressure = evt.penmotion.pressure
                        drawState.tiltX = evt.penmotion.tiltx
                        drawState.tiltY = evt.penmotion.tilty
                        DrawPenStroke(renderer, evt.penmotion.x, evt.penmotion.y)
                    Case SDL_EVENT_PEN_AXIS
                        ' 处理轴事件(如压力轴变化)
                        If (evt.penaxis.axis = SDL_PEN_AXIS_PRESSURE) Then
                            drawState.pressure = evt.penaxis.value
                        ElseIf (evt.penaxis.axis = SDL_PEN_AXIS_TILT_X) Then
                            drawState.tiltX = evt.penaxis.value
                        ElseIf (evt.penaxis.axis = SDL_PEN_AXIS_TILT_Y) Then
                            drawState.tiltY = evt.penaxis.value
                        End If
                End Select

            ' 区分原生鼠标和手写笔模拟鼠标
            Case SDL_EVENT_MOUSE_BUTTON_DOWN
                If (evt.button.which = SDL_PEN_MOUSEID) Then
                    SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "鼠标点击(手写笔模拟):位置(%d, %d)", evt.button.x, evt.button.y)
                    ' 模拟手写笔按下
                    drawState.isDrawing = True
                    drawState.lastX = evt.button.x
                    drawState.lastY = evt.button.y
                    drawState.pressure = 0.5 ' 默认压力值
                Else
                    SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "鼠标点击(原生):位置(%d, %d)", evt.button.x, evt.button.y)
                End If
            Case SDL_EVENT_MOUSE_BUTTON_UP
                If (evt.button.which = SDL_PEN_MOUSEID) Then
                    drawState.isDrawing = False
                End If
            Case SDL_EVENT_MOUSE_MOTION
                If (evt.motion.state And SDL_BUTTON_LMASK) Then
                    If (evt.motion.which = SDL_PEN_MOUSEID) Then
                        ' 模拟手写笔移动
                        drawState.pressure = 0.5 + (Sin(SDL_GetTicks() * 0.001) * 0.4) ' 模拟压力变化
                        DrawPenStroke(renderer, evt.motion.x, evt.motion.y)
                    End If
                End If
        End Select
    Wend

    ' 刷新画面
    SDL_RenderPresent(renderer)

    SDL_Delay(16) ' 约60FPS
Wend

' 4. 清理资源
SDL_DestroyRenderer(renderer)
SDL_DestroyWindow(window)
SDL_Quit()

SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序正常退出")
End 0

' 辅助函数:将手写笔轴枚举转换为可读字符串(SDL3 内置接口封装)
Function SDL_GetStringForPenAxis(ByVal axis As SDL_PenAxis) As ZString Ptr
    Select Case axis
        Case SDL_PEN_AXIS_PRESSURE: Return SDL_strdup("压力轴")
        Case SDL_PEN_AXIS_X: Return SDL_strdup("X轴")
        Case SDL_PEN_AXIS_Y: Return SDL_strdup("Y轴")
        Case SDL_PEN_AXIS_TILT_X: Return SDL_strdup("X倾斜轴")
        Case SDL_PEN_AXIS_TILT_Y: Return SDL_strdup("Y倾斜轴")
        Case SDL_PEN_AXIS_ROTATION: Return SDL_strdup("旋转轴")
        Case SDL_PEN_AXIS_DISTANCE: Return SDL_strdup("距离轴")
        Case Else: Return SDL_strdup("未知轴")
    End Select
End Function

核心知识点补充

  1. 手写笔轴类型说明 轴枚举 取值范围 说明
    SDL_PEN_AXIS_PRESSURE 0.0~1.0 压感值(0=无压力,1=最大)
    SDL_PEN_AXIS_TILT_X -1.0~1.0 X轴倾斜角度(左右倾斜)
    SDL_PEN_AXIS_TILT_Y -1.0~1.0 Y轴倾斜角度(前后倾斜)
    SDL_PEN_AXIS_ROTATION 0.0~1.0 旋转角度(笔杆旋转)
    SDL_PEN_AXIS_DISTANCE 0.0~1.0 与数位板的距离(仅接近事件有效)
  2. 压感绘图优化技巧

    • 压感值映射为线条宽度/透明度,模拟真实画笔效果;
    • 倾斜角度可调整线条颜色/纹理方向,提升绘图真实感;
    • 处理手写笔移动事件时,需插值补点避免线条断裂(高速移动时)。
  3. 模拟输入适配

    • 无手写笔硬件时,可通过 SDL_PEN_MOUSEID 识别手写笔模拟的鼠标事件;
    • 示例中模拟了鼠标输入的压感变化,便于无硬件时测试绘图逻辑;
    • 移动平台上,手写笔事件会自动转换为触摸/鼠标事件,无需额外适配。

总结

  1. 核心优势
    • SDL 统一封装了不同平台的手写笔事件,无需关注底层驱动差异;
    • 支持压感、倾斜、旋转等高级特性,满足专业绘图场景需求;
    • 通过 SDL_PEN_MOUSEID 可区分原生鼠标和手写笔模拟输入;
  2. 使用建议
    • 优先监听 SDL_EVENT_PEN_* 系列事件处理精准手写笔输入;
    • 针对不同平台的功能差异做兼容处理,避免依赖特定平台的特性;
    • 无手写笔硬件时,可通过鼠标模拟输入验证核心逻辑;
  3. 关键接口
    • 事件处理:SDL_EVENT_PEN_DOWN/UP/MOTION 等核心事件;
    • 设备信息:SDL_GetPenDeviceType 获取设备类型;
    • 输入区分:SDL_PEN_MOUSEID 宏区分模拟/原生输入。

评论一下?

OωO
取消