123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604 |
- unit GR32.Paint.Controller;
- (* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1 or LGPL 2.1 with linking exception
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * Alternatively, the contents of this file may be used under the terms of the
- * Free Pascal modified version of the GNU Lesser General Public License
- * Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
- * of this license are applicable instead of those above.
- * Please see the file LICENSE.txt for additional information concerning this
- * license.
- *
- * The Original Code is Paint tools for Graphics32
- *
- * The Initial Developer of the Original Code is
- * Anders Melander, [email protected]
- *
- * Portions created by the Initial Developer are Copyright (C) 2008-2025
- * the Initial Developer. All Rights Reserved.
- *
- * ***** END LICENSE BLOCK ***** *)
- interface
- {$INCLUDE GR32.inc}
- uses
- Classes,
- Controls,
- GR32,
- GR32_System,
- GR32_Image,
- GR32.Paint.Host.API,
- GR32.Paint.Tool.API,
- GR32.Paint.Controller.API;
- //------------------------------------------------------------------------------
- //
- // TCustomBitmap32PaintController
- //
- //------------------------------------------------------------------------------
- // Sample paint controller base class.
- //------------------------------------------------------------------------------
- type
- TCustomBitmap32PaintController = class(TInterfacedObject, IBitmap32PaintController)
- strict private
- FPaintHost: IBitmap32PaintHost;
- // Cached feature capabilities
- FFeatureCursor: IBitmap32PaintFeatureCursor;
- FFeatureVectorCursor: IBitmap32PaintFeatureVectorCursor;
- strict protected
- property PaintHost: IBitmap32PaintHost read FPaintHost;
- strict private
- // Tool
- FPaintTool: IBitmap32PaintTool; // The selected tool
- FActivePaintTool: IBitmap32PaintTool; // The tool that is currently handling mouse messages. Nil if none.
- FActivePaintToolContext: IBitmap32PaintToolContext;
- strict protected
- procedure SetActivePaintTool(const Value: IBitmap32PaintTool);
- property PaintTool: IBitmap32PaintTool read FPaintTool;
- property ActivePaintTool: IBitmap32PaintTool read FActivePaintTool;
- property ActivePaintToolContext: IBitmap32PaintToolContext read FActivePaintToolContext;
- strict protected
- // Cursor
- procedure ShowToolCursor(AShow, ATransientChange: Boolean);
- procedure UpdateToolCursor;
- strict protected
- // IBitmap32PaintController
- function BeginOperation(const Context: IBitmap32PaintToolContext): boolean;
- function ContinueOperation(const Context: IBitmap32PaintToolContext): boolean;
- procedure EndOperation(Complete: boolean);
- procedure MouseDown(const Context: IBitmap32PaintToolContext; Button: TMouseButton);
- procedure MouseMove(const Context: IBitmap32PaintToolContext);
- procedure MouseUp(const Context: IBitmap32PaintToolContext; Button: TMouseButton);
- procedure MouseEnter;
- procedure MouseExit;
- function CreateToolContext: IBitmap32PaintToolContext;
- function GetPaintTool: IBitmap32PaintTool;
- procedure SetPaintTool(const Value: IBitmap32PaintTool);
- function GetActivePaintTool: IBitmap32PaintTool;
- function GetActivePaintToolContext: IBitmap32PaintToolContext;
- public
- constructor Create(const APaintHost: IBitmap32PaintHost);
- destructor Destroy; override;
- end;
- //------------------------------------------------------------------------------
- //
- // TBitmap32PaintController
- //
- //------------------------------------------------------------------------------
- // An example paint controller optimized for use with TImage32 and TImgView32.
- //------------------------------------------------------------------------------
- type
- TBitmap32PaintController = class(TCustomBitmap32PaintController, IBitmap32PaintController)
- strict private
- FImage: TCustomImage32;
- strict private
- // Update optimization
- FUpdateTimer: TStopwatch;
- strict protected
- function GetHasCapture: boolean;
- procedure SetHasCapture(const Value: boolean);
- strict protected
- // IBitmap32PaintController
- function BeginOperation(const Context: IBitmap32PaintToolContext): boolean;
- function ContinueOperation(const Context: IBitmap32PaintToolContext): boolean;
- procedure EndOperation(Complete: boolean);
- property HasCapture: boolean read GetHasCapture write SetHasCapture;
- public
- constructor Create(AImage: TCustomImage32; const APaintHost: IBitmap32PaintHost = nil);
- end;
- //------------------------------------------------------------------------------
- //
- // Global settings
- //
- //------------------------------------------------------------------------------
- var
- // Max time between repaints during MouseMove/ContinueOperation
- Bitmap32PaintControllerMaxUpdateInterval: Cardinal = 50; // mS (zero for continuous update)
- //------------------------------------------------------------------------------
- //------------------------------------------------------------------------------
- //------------------------------------------------------------------------------
- implementation
- uses
- {$if defined(MSWINDOWS)}
- Windows,
- {$ifend}
- SysUtils,
- GR32.Paint.Host;
- type
- TImage32Cracker = class(TCustomImage32);
- //------------------------------------------------------------------------------
- //
- // TCustomBitmap32PaintController
- //
- //------------------------------------------------------------------------------
- constructor TCustomBitmap32PaintController.Create(const APaintHost: IBitmap32PaintHost);
- begin
- inherited Create;
- FPaintHost := APaintHost;
- // Cache feature capabilities so we don't have to resolve these continously
- if (not Supports(FPaintHost, IBitmap32PaintFeatureCursor, FFeatureCursor)) then
- FFeatureCursor := nil;
- if (not Supports(FPaintHost, IBitmap32PaintFeatureVectorCursor, FFeatureVectorCursor)) then
- FFeatureVectorCursor := nil;
- end;
- destructor TCustomBitmap32PaintController.Destroy;
- begin
- SetPaintTool(nil);
- FPaintTool := nil; // In case the above failed
- FActivePaintTool := nil;
- FActivePaintToolContext := nil;
- inherited;
- end;
- //------------------------------------------------------------------------------
- function TCustomBitmap32PaintController.CreateToolContext: IBitmap32PaintToolContext;
- var
- Tool: IBitmap32PaintTool;
- begin
- if (FActivePaintTool <> nil) then
- Tool := FActivePaintTool
- else
- Tool := FPaintTool;
- if (Tool <> nil) then
- Result := FPaintHost.CreateToolContext(Tool)
- else
- Result := nil;
- end;
- //------------------------------------------------------------------------------
- function TCustomBitmap32PaintController.GetActivePaintTool: IBitmap32PaintTool;
- begin
- Result := FActivePaintTool;
- end;
- function TCustomBitmap32PaintController.GetActivePaintToolContext: IBitmap32PaintToolContext;
- begin
- Result := FActivePaintToolContext;
- end;
- procedure TCustomBitmap32PaintController.SetActivePaintTool(const Value: IBitmap32PaintTool);
- begin
- FActivePaintToolContext := nil;
- FActivePaintTool := Value;
- end;
- //------------------------------------------------------------------------------
- function TCustomBitmap32PaintController.GetPaintTool: IBitmap32PaintTool;
- begin
- Result := FPaintTool;
- end;
- procedure TCustomBitmap32PaintController.SetPaintTool(const Value: IBitmap32PaintTool);
- var
- Continue: boolean;
- begin
- if (Value = FPaintTool) then
- exit;
- if (FActivePaintTool <> nil) then
- EndOperation(False);
- // Only activate new tool if we managed to deactivate old tool - or if there was no old tool
- if (FActivePaintTool = nil) then
- begin
- // Hide old cursor
- ShowToolCursor(False, False);
- if (FPaintTool <> nil) then
- begin
- FPaintTool.Deactivate;
- FPaintTool := nil;
- end;
- if (Value <> nil) then
- begin
- Continue := True;
- Value.Activate(Continue);
- if (Continue) then
- begin
- FPaintTool := Value;
- // Display new cursor
- UpdateToolCursor;
- end;
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TCustomBitmap32PaintController.ShowToolCursor(AShow, ATransientChange: boolean);
- begin
- if (FFeatureCursor <> nil) then
- FFeatureCursor.ShowToolCursor(AShow, ATransientChange);
- end;
- procedure TCustomBitmap32PaintController.UpdateToolCursor;
- var
- NewCursor: TCursor;
- begin
- if (FFeatureCursor = nil) then
- exit;
- // Note: Calling FPaintTool.GetCursor will automatically create
- // a complex cursor, if the tool supplies one.
- if (FPaintTool = nil) or (not FPaintTool.GetCursor(NewCursor)) then
- NewCursor := crDefault;
- FFeatureCursor.SetToolCursor(NewCursor);
- ShowToolCursor(True, False);
- end;
- //------------------------------------------------------------------------------
- function TCustomBitmap32PaintController.BeginOperation(const Context: IBitmap32PaintToolContext): boolean;
- var
- ToolState: TBitmap32PaintToolState;
- Continue: boolean;
- begin
- Assert(FPaintTool <> nil);
- Continue := True;
- Result := False;
- FPaintTool.BeginTool(Continue);
- try
- if (not Continue) then
- begin
- ToolState := tsAbort;
- Exit;
- end;
- ToolState := tsContinue;
- try
- FPaintTool.BeginAction(Context, ToolState);
- except
- ToolState := tsAbort;
- FPaintTool.EndAction(Context, ToolState);
- raise;
- end;
- case ToolState of
- tsComplete:
- ;
- tsAbort:
- ;
- tsContinue:
- begin
- SetActivePaintTool(FPaintTool);
- FActivePaintToolContext := Context;
- Result := True;
- end;
- end;
- finally
- if (not Result) then
- begin
- FPaintTool.EndTool;
- SetActivePaintTool(nil);
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- function TCustomBitmap32PaintController.ContinueOperation(const Context: IBitmap32PaintToolContext): boolean;
- var
- ToolState: TBitmap32PaintToolState;
- begin
- if (FActivePaintTool = nil) then
- raise Exception.Create('Operation is not in progress');
- Result := False;
- ToolState := tsContinue;
- try
- try
- FActivePaintTool.ContinueAction(FActivePaintToolContext, ToolState);
- // Note: EndOperation may be called from Tool.ContinueAction so we must
- // not assume that the tool is still active when ContinueAction returns.
- if (FActivePaintTool = nil) then
- exit;
- except
- ToolState := tsAbort;
- raise;
- end;
- case ToolState of
- tsComplete:
- ;
- tsAbort:
- ;
- tsContinue:
- Result := True;
- end;
- finally
- if (not Result) and (FActivePaintTool <> nil) then
- begin
- FActivePaintTool.EndTool;
- SetActivePaintTool(nil);
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TCustomBitmap32PaintController.EndOperation(Complete: boolean);
- var
- ToolState: TBitmap32PaintToolState;
- begin
- if (FActivePaintTool = nil) then
- exit; // TODO : Is this an error condition?
- if (Complete) then
- ToolState := tsComplete
- else
- ToolState := tsAbort;
- try
- try
- FActivePaintTool.EndAction(FActivePaintToolContext, ToolState);
- if (FActivePaintTool = nil) then
- exit;
- except
- ToolState := tsAbort;
- raise;
- end;
- case ToolState of
- tsComplete:
- ;
- tsAbort:
- ;
- end;
- finally
- if (ToolState <> tsContinue) and (FActivePaintTool <> nil) then
- begin
- FActivePaintTool.EndTool;
- SetActivePaintTool(nil);
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TCustomBitmap32PaintController.MouseEnter;
- begin
- // Enable vector cursor
- ShowToolCursor(True, True);
- end;
- procedure TCustomBitmap32PaintController.MouseExit;
- begin
- // Disable vector cursor
- ShowToolCursor(False, True);
- end;
- //------------------------------------------------------------------------------
- procedure TCustomBitmap32PaintController.MouseDown(const Context: IBitmap32PaintToolContext; Button: TMouseButton);
- begin
- if (Context.PaintTool <> nil) then
- Context.PaintTool.MouseDown(Context, Button);
- end;
- procedure TCustomBitmap32PaintController.MouseMove(const Context: IBitmap32PaintToolContext);
- begin
- if (Context.PaintTool <> nil) then
- Context.PaintTool.MouseMove(Context);
- // Update cursor layer with most recent position
- if (FFeatureVectorCursor <> nil) then
- FFeatureVectorCursor.MoveToolVectorCursor(Context.MouseParams.ViewPortPos);
- end;
- procedure TCustomBitmap32PaintController.MouseUp(const Context: IBitmap32PaintToolContext; Button: TMouseButton);
- begin
- if (Context.PaintTool <> nil) then
- Context.PaintTool.MouseUp(Context, Button);
- end;
- //------------------------------------------------------------------------------
- //
- // TBitmap32PaintController
- //
- //------------------------------------------------------------------------------
- constructor TBitmap32PaintController.Create(AImage: TCustomImage32; const APaintHost: IBitmap32PaintHost);
- var
- PaintHost: IBitmap32PaintHost;
- begin
- if (APaintHost = nil) then
- // Embed
- PaintHost := TBitmap32PaintHost.Create(AImage)
- else
- // Inject
- PaintHost := APaintHost;
- inherited Create(PaintHost);
- FImage := AImage;
- end;
- //------------------------------------------------------------------------------
- function TBitmap32PaintController.BeginOperation(const Context: IBitmap32PaintToolContext): boolean;
- begin
- Result := inherited;
- if (Result) then
- begin
- // Reacquire capture in case tool did something stupid that caused us to lose it (e.g. Move Select tool)
- if (not (betfMouseCapture in PaintTool.ToolFeatures)) and (not HasCapture) then
- HasCapture := True;
- end;
- end;
- //------------------------------------------------------------------------------
- function TBitmap32PaintController.ContinueOperation(const Context: IBitmap32PaintToolContext): boolean;
- begin
- Result := inherited;
- if (Result) then
- begin
- // Reacquire capture in case tool did something stupid that caused us to lose it (e.g. Move Select tool)
- if (not (betfMouseCapture in PaintTool.ToolFeatures)) and (not HasCapture) then
- HasCapture := True;
- end;
- // Repaint ASAP to avoid lag caused by continous mouse messages during the operation.
- // The WM_PAINT messages are only generated once the message queue is otherwise empty or
- // UpdateWindow is called.
- if (not TImage32Cracker(FImage).CacheValid) or (not TImage32Cracker(FImage).BufferValid) then
- begin
- // Buffer has been invalidated. Limit how long we wait for an update.
- if (Bitmap32PaintControllerMaxUpdateInterval = 0) then
- // No wait; Update immediately
- FImage.Update
- else
- if (FUpdateTimer.IsRunning) then
- begin
- // Already waiting; Have we waited long enough?
- if (FUpdateTimer.ElapsedMilliseconds > Bitmap32PaintControllerMaxUpdateInterval) then
- begin
- FImage.Update;
- FUpdateTimer.Stop;
- end;
- end else
- // Not already waiting; Start timer
- FUpdateTimer := TStopwatch.StartNew;
- end else
- // Nothing to update; Don't wait.
- FUpdateTimer.Stop;
- end;
- //------------------------------------------------------------------------------
- procedure TBitmap32PaintController.EndOperation(Complete: boolean);
- begin
- inherited;
- // Ensure mouse capture is released (this takes care of right-button which TImage32 doesn't handle properly)
- // TODO : I'm not sure that this is necessary anymore but there's no harm in it
- if (ActivePaintTool = nil) and (HasCapture) then
- HasCapture := False;
- end;
- //------------------------------------------------------------------------------
- function TBitmap32PaintController.GetHasCapture: boolean;
- begin
- {$if defined(MSWINDOWS)}
- Result := (GetCapture = FImage.Handle);
- {$else}
- Result := (GetCaptureControl = FImage);
- {$ifend}
- end;
- procedure TBitmap32PaintController.SetHasCapture(const Value: boolean);
- begin
- {$if defined(MSWINDOWS)}
- if (Value) then
- SetCapture(FImage.Handle)
- else
- ReleaseCapture;
- {$else}
- if (Value) then
- SetCaptureControl(FImage)
- else
- SetCaptureControl(nil);
- {$ifend}
- end;
- //------------------------------------------------------------------------------
- end.
|