fruit-popper/GDLIB/GDMOUSE.PAS

474 lines
12 KiB
Plaintext
Raw Normal View History

{ GDlib Mouse Handler
Gered King, 2018 }
{$A+,B-,F-,G+,I-,N+,P-,Q-,R-,S-,T-,V-,X+}
unit GDMouse;
interface
type
MouseButton = byte;
CursorBitmap = array[0..63] of byte;
PCursorBitmap = ^CursorBitmap;
const
MOUSE_LEFT_BUTTON = $01;
MOUSE_RIGHT_BUTTON = $02;
MOUSE_MIDDLE_BUTTON = $04;
CURSOR_WIDTH = 16;
CURSOR_HEIGHT = 16;
MouseX : integer = 0;
MouseY : integer = 0;
MouseButtons : word = 0;
MouseDeltaX : integer = 0;
MouseDeltaY : integer = 0;
function InitMouse : boolean;
function CloseMouse : boolean;
function IsMouseInitialized : boolean;
function IsMousePresent : boolean;
procedure ShowMouse;
procedure HideMouse;
procedure SetMouseBounds(minX, minY, maxX, maxY: integer);
procedure SetMouseCursor(const bitmap: PCursorBitmap; hotspotX, hotspotY: integer);
procedure DrawMouseCursor;
implementation
uses GDGfx, GDClip, GDEvents, Toolbox;
const
MOUSE_FILL_COLOR = 15;
MOUSE_OUTLINE_COLOR = 0;
_hasMouse : boolean = false;
_mouseInstalled : boolean = false;
_mouseVisible : boolean = false;
_cursorHotspotX : integer = 0;
_cursorHotspotY : integer = 0;
{ copy of the default microsoft mouse driver's cursor in graphics modes }
_defaultCursorBitmap : CursorBitmap = (
$ff,$3f,$ff,$1f,$ff,$0f,$ff,$07,
$ff,$03,$ff,$01,$ff,$00,$7f,$00,
$3f,$00,$1f,$00,$ff,$01,$ff,$00,
$ff,$30,$7f,$f8,$7f,$f8,$ff,$fc,
$00,$00,$00,$40,$00,$60,$00,$70,
$00,$78,$00,$7c,$00,$7e,$00,$7f,
$80,$7f,$00,$7c,$00,$6c,$00,$46,
$00,$06,$00,$03,$00,$03,$00,$00
);
var
_currentCursorBitmap : CursorBitmap;
_mouseEvent : PInputEvent;
_mousePrevButtons : word;
procedure ResetMouseState;
begin
MouseX := 0;
MouseY := 0;
MouseButtons := 0;
_mousePrevButtons := 0;
MouseDeltaX := 0;
MouseDeltaY := 0;
end;
function InitMouseDriver : boolean;
begin
asm
mov ax, 0
int 33h
mov @Result, al { ax = 0 if it failed }
end;
{ reset our copy of the current mouse cursor bitmap at the same time }
MemCopy(@_currentCursorBitmap, @_defaultCursorBitmap, SizeOf(CursorBitmap));
_cursorHotspotX := 0;
_cursorHotspotY := 0;
end;
procedure UpdateMouseState;
assembler;
asm
mov ax, 3
int 33h
shr cx, 1
mov MouseX, cx { MouseX = cx / 2 }
mov MouseY, dx
mov ax, MouseButtons
mov _mousePrevButtons, ax { MousePrevButtons = MouseButtons }
mov MouseButtons, bx
mov MouseDeltaX, 0
mov MouseDeltaY, 0
end;
procedure PushMotionEvent;
begin
_mouseEvent := PushEvent;
with _mouseEvent^ do begin
Event := EVENT_TYPE_MOUSE_MOTION;
MM_X := MouseX;
MM_Y := MouseY;
MM_DeltaX := MouseDeltaX;
MM_DeltaY := MouseDeltaY;
MM_Buttons := MouseButtons;
end;
end;
procedure PushButtonEvent(action: EventAction; button: MouseButton);
begin
_mouseEvent := PushEvent;
with _mouseEvent^ do begin
Event := EVENT_TYPE_MOUSE_BUTTON;
MB_X := MouseX;
MB_Y := MouseY;
MB_Action := action;
MB_Button := button;
end;
end;
procedure ProcessMouseEvents;
begin
if not IsEventsInitialized then
exit;
if (MouseDeltaX <> 0) or (MouseDeltaY <> 0) then
PushMotionEvent;
if MouseButtons <> _mousePrevButtons then begin
if (MouseButtons and MOUSE_LEFT_BUTTON) <>
(_mousePrevButtons and MOUSE_LEFT_BUTTON) then begin
if (MouseButtons and MOUSE_LEFT_BUTTON) > 0 then
PushButtonEvent(EVENT_ACTION_PRESSED, MOUSE_LEFT_BUTTON)
else
PushButtonEvent(EVENT_ACTION_RELEASED, MOUSE_LEFT_BUTTON);
end;
if (MouseButtons and MOUSE_RIGHT_BUTTON) <>
(_mousePrevButtons and MOUSE_RIGHT_BUTTON) then begin
if (MouseButtons and MOUSE_RIGHT_BUTTON) > 0 then
PushButtonEvent(EVENT_ACTION_PRESSED, MOUSE_RIGHT_BUTTON)
else
PushButtonEvent(EVENT_ACTION_RELEASED, MOUSE_RIGHT_BUTTON);
end;
if (MouseButtons and MOUSE_MIDDLE_BUTTON) <>
(_mousePrevButtons and MOUSE_MIDDLE_BUTTON) then begin
if (MouseButtons and MOUSE_MIDDLE_BUTTON) > 0 then
PushButtonEvent(EVENT_ACTION_PRESSED, MOUSE_MIDDLE_BUTTON)
else
PushButtonEvent(EVENT_ACTION_RELEASED, MOUSE_MIDDLE_BUTTON);
end;
end;
end;
procedure MouseHandler;
{ our custom mouse handler.
this is written as an 'assembler' proc because the mouse driver passes it
the mouse status via registers. using a pascal 'interrupt' proc would
kind of work also, except that we would have to do a hacky return to ensure
the registers are popped correctly and that a 'retf' is used for the
return (instead of an 'iret' which is what pascal would otherwise do).
doing this with an 'assembler' proc instead just seemed cleaner to me... }
far;
assembler;
asm
{ inputs provided by mouse driver:
AX = event trigger bit
BX = button state
CX = X coordinate
DX = Y coordinate
DI = horizontal mickeys
SI = vertical mickeys }
pusha
mov ax, seg @Data { restore DS so that we can access pascal vars }
mov ds, ax
shr cx, 1 { mouse x coordinate is doubled for some reason }
mov ax, MouseButtons
mov _mousePrevButtons, ax { MousePrevButtons := MouseButtons }
mov MouseButtons, bx
mov ax, cx
sub ax, MouseX
mov MouseDeltaX, ax { MouseDeltaX := CX - MouseX }
mov ax, dx
sub ax, MouseY
mov MouseDeltaY, ax { MouseDeltaY := DX - MouseX }
mov MouseX, cx
mov MouseY, dx
call ProcessMouseEvents
popa
end;
function InitMouse : boolean;
{ initializes the mouse driver (if there is one present) and then installs
our custom mouse event handler. returns true if the mouse was set up
successfully or if there is no mouse device connected (check
IsMousePresent() to be sure). returns false if an error occured setting
up the mouse or if it was already initialized. }
begin
if _mouseInstalled then begin
InitMouse := false;
exit;
end;
ResetMouseState;
_hasMouse := InitMouseDriver;
if not _hasMouse then begin
_mouseInstalled := true;
InitMouse := true;
exit;
end;
UpdateMouseState;
asm
mov ax, 0ch
mov cx, 31
mov dx, seg MouseHandler
mov es, dx
mov dx, offset MouseHandler
int 33h
end;
_mouseVisible := false;
_mouseInstalled := true;
InitMouse := true;
end;
function CloseMouse : boolean;
{ removes a previously installed custom mouse event handler and resets the
mouse driver to it's initial state. }
begin
if not _mouseInstalled then begin
CloseMouse := true;
exit;
end;
if not _hasMouse then begin
_mouseInstalled := false;
CloseMouse := true;
exit;
end;
asm
mov ax, 0ch
mov cx, 0
int 33h
end;
ResetMouseState;
InitMouseDriver;
_mouseVisible := false;
_mouseInstalled := false;
CloseMouse := true;
end;
function IsMouseInitialized : boolean;
{ returns true if the mouse driver was initialized successfully }
begin
IsMouseInitialized := _mouseInstalled;
end;
function IsMousePresent : boolean;
{ returns true if a mouse device is currently connected to the computer }
begin
IsMousePresent := _hasMouse;
end;
procedure ShowMouse;
{ shows the mouse cursor. if the mouse cursor is currently shown, this does
nothing }
begin
if not _hasMouse then
exit;
if _mouseVisible then
exit;
asm
mov ax, 1
int 33h
end;
_mouseVisible := true;
end;
procedure HideMouse;
{ hides the mouse cursor. if the mouse cursor is not currently shown, this
does nothing }
begin
if not _hasMouse then
exit;
if not _mouseVisible then
exit;
asm
mov ax, 2
int 33h;
end;
_mouseVisible := false;
end;
procedure SetMouseBounds(minX, minY, maxX, maxY: integer);
{ sets the pixel boundaries for the mouse cursor }
begin
if not _hasMouse then
exit;
asm
mov ax, 7
mov cx, minX
mov dx, maxX
int 33h
mov ax, 8
mov cx, minY
mov dx, maxY
int 33h
end;
end;
procedure SetMouseCursor(const bitmap: PCursorBitmap;
hotspotX, hotspotY: integer);
{ changes the mouse cursor to the specified bitmap. if the bitmap is nil,
resets the cursor back to the original bitmap }
begin
{ make a copy of the bitmap, solely for DrawMouseCursor to use it.
(there is no mouse driver function for reading the current cursor
bitmap?) }
if bitmap = nil then
MemCopy(@_currentCursorBitmap, @_defaultCursorBitmap, SizeOf(CursorBitmap))
else
MemCopy(@_currentCursorBitmap, bitmap, SizeOf(CursorBitmap));
_cursorHotspotX := hotspotX;
_cursorHotspotY := hotspotY;
asm
mov ax, 9h
mov bx, hotspotX
mov cx, hotspotY
mov dx, seg _currentCursorBitmap
mov es, dx
mov dx, offset _currentCursorBitmap
int 33h
end;
end;
procedure LLBlitMouseCursor(width, height, destYinc, leftClipBits: integer;
destOffs, srcSeg, srcOffs: word);
assembler;
asm
push ds
mov dx, height { dx = line loop counter }
test dx, dx { stop now if there's zero lines to draw }
jz @done
call far ptr GetBoundLayerSegment
mov es, ax
mov di, destOffs
mov ds, srcSeg
mov si, srcOffs
@draw_line:
mov ax, ds:[si] { load next screen mask word }
mov bx, ds:[si+32] { load next shape mask word }
mov cx, leftClipBits
test cx, cx
jz @done_bits_clipping { trim off left bits (for clipping) if needed }
shl ax, cl
shl bx, cl
@done_bits_clipping:
mov cx, width { cx = number of pixels to draw }
{ ax = screen mask word }
{ bx = shape mask word }
@draw_pixel:
shl ax, 1
jc @skip_pixel { screen mask bit is 1? skip this pixel }
shl bx, 1
jnc @draw_cursor_outline_px
@draw_cursor_color_px: { draw inner color if shape bit is 1 }
mov es:[di], byte(MOUSE_FILL_COLOR)
jmp @end_pixel
@draw_cursor_outline_px: { or, draw outline color if shape bit is 0 }
mov es:[di], byte(MOUSE_OUTLINE_COLOR)
jmp @end_pixel
@skip_pixel:
shl bx, 1 { skipping this pixel, need to also shift the }
{ shape mask word to keep them both at the }
{ same location }
@end_pixel:
inc di { move to the next pixel }
dec cx { decrease pixel drawing counter }
jnz @draw_pixel
@end_line:
add si, 2 { move to next line }
add di, destYinc
dec dx { decrease line loop counter }
jnz @draw_line
@done:
pop ds
end;
procedure DrawMouseCursor;
{ draws the mouse cursor at it's current location. this draws it completely
independently from the mouse driver. it should only be used after a call
to HideMouse and when your application is redrawing the entire screen
every frame. otherwise, the built-in mouse driver cursor rendering is
probably the best choice. }
var
x, y: integer;
srcX, srcY, srcWidth, srcHeight: integer;
srcOffs, destOffs: word;
destYinc: integer;
begin
if not _mouseInstalled then
exit;
{ destination always at current mouse cursor position (adjusted by
hotspot X/Y, which is how the normal mouse driver does it) }
x := MouseX - _cursorHotspotX;
y := MouseY - _cursorHotspotY;
srcX := 0;
srcY := 0;
srcWidth := CURSOR_WIDTH;
srcHeight := CURSOR_HEIGHT;
if not ClipBlitToScreen(srcX, srcY, srcWidth, srcHeight, x, y) then
exit;
srcOffs := Ofs(_currentCursorBitmap[srcY * 2]);
destOffs := GetBoundLayerOffsetAt(x, y);
destYinc := SCREEN_WIDTH - srcWidth;
LLBlitMouseCursor(srcWidth, srcHeight,
destYinc, srcX,
destOffs,
Seg(_currentCursorBitmap), srcOffs);
end;
end.