sync: restore source from Google Drive
Sync changed files to Google Drive / sync (push) Successful in 1h45m13s

This commit is contained in:
admin_d committed 2026-09-12 16:50:30 +05:00
1 parent e2d8247e91
commit 7063d1b1c9
993 files changed
+949976

No files matched your search

File diff suppressed because it is too large. Load diff
+848
View File
@@ -0,0 +1,848 @@
unit KM_ControlsBase;
{$I KaM_Remake.inc}
interface
uses
Classes, Controls,
KromOGLUtils,
KM_Controls,
KM_RenderUI,
KM_ResFonts, KM_ResTypes,
KM_CommonTypes, KM_Points;
type
// Beveled area
TKMBevel = class(TKMControl)
const
DEF_BACK_ALPHA = 0.4;
DEF_EDGE_ALPHA = 0.75;
public
BackAlpha: Single;
EdgeAlpha: Single;
Color: TKMColor3f;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer);
procedure SetDefBackAlpha;
procedure SetDefEdgeAlpha;
procedure SetDefColor;
procedure Paint; override;
end;
// Rectangle area
TKMShape = class(TKMControl)
public
FillColor: TColor4;
LineColor: TColor4; //color of outline
LineWidth: Byte;
public
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer);
procedure Paint; override;
end;
// Text Label
TKMLabel = class(TKMControl)
private
fWordWrap: Boolean;
fFont: TKMFont;
fFontColor: TColor4; //Usually white (self-colored)
fCaption: UnicodeString; //Original text
fText: UnicodeString; //Reformatted text
fTextAlign: TKMTextAlign;
fTextVAlign: TKMTextVAlign;
fTextSize: TKMPoint;
fStrikethrough: Boolean;
fTabWidth: Integer;
procedure SetCaption(const aCaption: UnicodeString);
procedure SetWordWrap(aValue: Boolean);
procedure ReformatText;
procedure SetFont(const Value: TKMFont);
protected
procedure SetWidth(aValue: Integer); override;
function GetIsPainted: Boolean; override;
public
MaxLines: Integer;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; const aCaption: UnicodeString;
aFont: TKMFont; aTextAlign: TKMTextAlign); overload;
constructor Create(aParent: TKMPanel; aLeft,aTop: Integer; const aCaption: UnicodeString; aFont: TKMFont;
aTextAlign: TKMTextAlign); overload;
function HitTest(X, Y: Integer; aIncludeDisabled: Boolean = False; aIncludeNotHitable: Boolean = False): Boolean; override;
procedure SetColor(aColor: Cardinal);
property WordWrap: Boolean read fWordWrap write SetWordWrap; //Whether to automatically wrap text within given text area width
property Caption: UnicodeString read fCaption write SetCaption;
property FontColor: TColor4 read fFontColor write fFontColor;
property Strikethrough: Boolean read fStrikethrough write fStrikethrough;
property TabWidth: Integer read fTabWidth write fTabWidth;
property TextSize: TKMPoint read fTextSize;
function TextLeft: Integer;
property TextVAlign: TKMTextVAlign read fTextVAlign write fTextVAlign;
property Font: TKMFont read fFont write SetFont;
procedure Paint; override;
end;
// Label that is scrolled within an area. Used in Credits
TKMLabelScroll = class(TKMLabel)
public
SmoothScrollToTop: Cardinal; //Delta between this and TimeGetTime affects vertical position
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; const aCaption: UnicodeString; aFont: TKMFont; aTextAlign: TKMTextAlign);
procedure Paint; override;
end;
// Image
TKMImage = class(TKMControl)
private
fRX: TRXType;
fTexID: Word;
fFlagColor: TColor4;
protected
function GetIsPainted: Boolean; override;
public
ImageAnchors: TKMAnchorsSet;
Highlight: Boolean;
HighlightOnMouseOver: Boolean;
HighlightCoef: Single;
Lightness: Single;
ClipToBounds: Boolean;
Tiled: Boolean;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aTexID: Word; aRX: TRXType = rxGui;
aImageAnchors: TKMAnchorsSet = [anLeft, anTop]);
property RX: TRXType read fRX write fRX;
property TexID: Word read fTexID write fTexID;
property FlagColor: TColor4 read fFlagColor write fFlagColor;
function Click: Boolean;
procedure ImageStretch;
procedure ImageCenter;
procedure Paint; override;
end;
// Image stack - for army formation view
TKMImageStack = class(TKMControl)
private
fRX: TRXType;
fTexID1, fTexID2: Word; //Normal and commander
fCount: Integer;
fColumns: Integer;
fDrawWidth: Integer;
fDrawHeight: Integer;
fHighlightID: Integer;
public
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aTexID1, aTexID2: Word; aRX: TRXType = rxGui);
procedure SetCount(aCount, aColumns, aHighlightID: Word);
procedure Paint; override;
end;
// 3D Button
TKMButton = class(TKMControl)
private
fCaption: UnicodeString;
fTextAlign: TKMTextAlign;
fStyle: TKMButtonStyle;
fRX: TRXType;
fAutoHeight: Boolean; //Set button height automatically depending text size (height)
procedure InitCommon(aStyle: TKMButtonStyle);
procedure SetCaption(const aCaption: UnicodeString);
procedure SetAutoHeight(aValue: Boolean);
procedure UpdateHeight;
public
FlagColor: TColor4; //When using an image
CapColor: TColor4;
Font: TKMFont;
MakesSound: Boolean;
TexID: Word;
CapOffsetX: Shortint;
CapOffsetY: Shortint;
ShowImageEnabled: Boolean; // show picture as enabled or not (normal or darkened)
TextVAlign: TKMTextVAlign;
AutoTextPadding: Byte; //text padding for autoHeight
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aTexID: Word; aRX: TRXType;
aStyle: TKMButtonStyle); overload;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; const aCaption: UnicodeString;
aStyle: TKMButtonStyle); overload;
function Click: Boolean; //Try to click a button and return TRUE if succeded
property Caption: UnicodeString read fCaption write SetCaption;
property AutoHeight: Boolean read fAutoHeight write SetAutoHeight;
procedure MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
procedure Paint; override;
end;
// Common Flat Button
TKMButtonFlatCommon = class abstract(TKMControl)
private
public
RX: TRXType;
TexID: Word;
TexOffsetX: Shortint;
TexOffsetY: Shortint;
CapOffsetX: Shortint;
CapOffsetY: Shortint;
Caption: UnicodeString;
CapColor: TColor4;
FlagColor: TColor4;
Font: TKMFont;
HideHighlight: Boolean;
Clickable: Boolean; //Disables clicking without dimming
EdgeAlpha: Single;
BackAlpha: Single;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight, aTexID: Integer; aRX: TRXType = rxGui);
procedure MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
procedure Paint; override;
end;
// FlatButton
TKMButtonFlat = class(TKMButtonFlatCommon)
public
Down: Boolean;
procedure Paint; override;
end;
// FlatButton with Shape on it
TKMFlatButtonShape = class(TKMControl)
private
fCaption: UnicodeString;
fFont: TKMFont;
fFontHeight: Byte;
public
Down: Boolean;
FontColor: TColor4;
ShapeColor: TColor4;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; const aCaption: UnicodeString; aFont: TKMFont; aShapeColor: TColor4);
procedure Paint; override;
end;
implementation
uses
{$IFDEF MSWindows} Windows, {$ENDIF}
{$IFDEF Unix} LCLIntf, LCLType, {$ENDIF}
StrUtils, Math,
KM_ControlsTypes,
KM_Sound,
KM_Resource, KM_ResSound, KM_ResSprites,
KM_Defaults, KM_CommonUtils,
KM_RenderTypes;
{ TKMBevel }
constructor TKMBevel.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
SetDefBackAlpha;
SetDefEdgeAlpha;
end;
procedure TKMBevel.SetDefBackAlpha;
begin
BackAlpha := DEF_BACK_ALPHA; //Default value
end;
procedure TKMBevel.SetDefEdgeAlpha;
begin
EdgeAlpha := DEF_EDGE_ALPHA; //Default value
end;
procedure TKMBevel.SetDefColor;
begin
Color := COLOR3F_BLACK; //Default value
end;
procedure TKMBevel.Paint;
begin
inherited;
TKMRenderUI.WriteBevel(AbsLeft, AbsTop, Width, Height, Color, EdgeAlpha, BackAlpha);
end;
{ TKMShape }
constructor TKMShape.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
LineWidth := 2;
end;
procedure TKMShape.Paint;
begin
inherited;
TKMRenderUI.WriteShape(AbsLeft, AbsTop, Width, Height, FillColor);
TKMRenderUI.WriteOutline(AbsLeft, AbsTop, Width, Height, LineWidth, LineColor);
end;
{ TKMLabel }
constructor TKMLabel.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; const aCaption: UnicodeString;
aFont: TKMFont; aTextAlign: TKMTextAlign);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
fFont := aFont;
fFontColor := $FFFFFFFF;
fTextAlign := aTextAlign;
fTextVAlign := tvaTop;
fWordWrap := False;
fTabWidth := FONT_TAB_WIDTH;
SetCaption(aCaption);
end;
//Same as above but with width/height ommitted, as in most cases we don't know/don't care
constructor TKMLabel.Create(aParent: TKMPanel; aLeft, aTop: Integer; const aCaption: UnicodeString; aFont: TKMFont;
aTextAlign: TKMTextAlign);
begin
Create(aParent, aLeft, aTop, 0, 0, aCaption, aFont, aTextAlign);
end;
function TKMLabel.TextLeft: Integer;
begin
case fTextAlign of
taLeft: Result := AbsLeft;
taCenter: Result := AbsLeft + Round((Width - fTextSize.X) / 2);
taRight: Result := AbsLeft + (Width - fTextSize.X);
else Result := AbsLeft;
end;
end;
procedure TKMLabel.SetCaption(const aCaption: UnicodeString);
begin
fCaption := aCaption;
ReformatText;
end;
procedure TKMLabel.SetWordWrap(aValue: Boolean);
begin
fWordWrap := aValue;
ReformatText;
end;
//Override usual hittest with regard to text alignment
function TKMLabel.HitTest(X, Y: Integer; aIncludeDisabled: Boolean = False; aIncludeNotHitable: Boolean = False): Boolean;
begin
Result := (Hitable or aIncludeNotHitable)
and InRange(X, TextLeft, TextLeft + fTextSize.X)
and InRange(Y, AbsTop, AbsTop + Height);
end;
procedure TKMLabel.SetColor(aColor: Cardinal);
begin
fCaption := StripColor(fCaption);
Caption := WrapColor(fCaption, aColor);
end;
procedure TKMLabel.SetFont(const Value: TKMFont);
begin
fFont := Value;
ReformatText;
end;
// Existing EOLs should be preserved, and new ones added where needed
// Keep original intact incase we need to Reformat text once again
procedure TKMLabel.ReformatText;
var
I: Integer;
stake: Integer;
begin
if fWordWrap then
fText := gRes.Fonts[fFont].WordWrap(fCaption, Width, True, False)
else
fText := fCaption;
// We may need to display only N lines
if MaxLines > 0 then
begin
stake := 0;
for I := 1 to MaxLines do
begin
stake := PosEx(#124, fText, stake+1);
if stake = 0 then
Break;
end;
if stake > 0 then
fText := LeftStr(fText, stake - 1);
end;
fTextSize := gRes.Fonts[fFont].GetTextSize(fText);
end;
procedure TKMLabel.SetWidth(aValue: Integer);
begin
inherited;
if fWordWrap then
ReformatText;
end;
function TKMLabel.GetIsPainted: Boolean;
begin
Result := inherited and (Length(fCaption) > 0 );
end;
// Send caption to render
procedure TKMLabel.Paint;
var
t: Integer;
col: Cardinal;
begin
inherited;
if Enabled then
col := FontColor
else
col := $FF888888;
t := 0;
if Height > 0 then
begin
case fTextVAlign of
tvaNone,
tvaTop: ;
tvaMiddle: t := (Height - fTextSize.Y) div 2;
tvaBottom: t := Height - fTextSize.Y;
end;
end;
TKMRenderUI.WriteText(AbsLeft, AbsTop + t, Width, fText, fFont, fTextAlign, col, False, False, False, fTabWidth);
if fStrikethrough then
TKMRenderUI.WriteShape(TextLeft, AbsTop + fTextSize.Y div 2 - 2, fTextSize.X, 3, col, $FF000000);
end;
{ TKMLabelScroll }
constructor TKMLabelScroll.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; const aCaption: UnicodeString; aFont: TKMFont; aTextAlign: TKMTextAlign);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight, aCaption, aFont, aTextAlign);
SmoothScrollToTop := 0; //Disabled by default
end;
procedure TKMLabelScroll.Paint;
var
newTop: Integer;
col: Cardinal;
begin
TKMRenderUI.SetupClipY(AbsTop, AbsTop + Height);
newTop := EnsureRange(AbsTop + Height - TimeSince(SmoothScrollToTop) div 50, -MINSHORT, MAXSHORT); //Compute delta and shift by it upwards (Credits page)
if Enabled then
col := FontColor
else
col := $FF888888;
TKMRenderUI.WriteText(AbsLeft, newTop, Width, fCaption, fFont, fTextAlign, col);
TKMRenderUI.ReleaseClipY;
end;
{ TKMImage }
constructor TKMImage.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aTexID: Word; aRX: TRXType = rxGui;
aImageAnchors: TKMAnchorsSet = [anLeft, anTop]);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
fRX := aRX;
fTexID := aTexID;
fFlagColor := $FFFF00FF;
ImageAnchors := aImageAnchors;
Highlight := False;
HighlightOnMouseOver := False;
HighlightCoef := CTRL_HIGHLIGHT_COEF_DEF;
end;
function TKMImage.GetIsPainted: Boolean;
begin
Result := inherited and (TexID <> 0);
end;
//DoClick is called by keyboard shortcuts
//It's important that Control must be:
// IsVisible (can't shortcut invisible/unaccessible button)
// Enabled (can't shortcut disabled function, e.g. Halt during fight)
function TKMImage.Click: Boolean;
begin
if Visible and Enabled then
begin
//Mark self as CtrlOver and CtrlUp, don't mark CtrlDown since MouseUp manually Nils it
Parent.MasterControl.CtrlOver := Self;
Parent.MasterControl.CtrlUp := Self;
if Assigned(OnClick) then OnClick(Self);
Result := True; //Click has happened
end
else
Result := False; //No, we couldn't click for Control is unreachable
end;
procedure TKMImage.ImageStretch;
begin
ImageAnchors := [anLeft, anRight, anTop, anBottom]; //Stretch image to fit
end;
procedure TKMImage.ImageCenter; //Render image from center
begin
ImageAnchors := [];
end;
{If image area is bigger than image - do center image in it}
procedure TKMImage.Paint;
var
x, y: Integer;
col, row: Integer;
paintLightness: Single;
drawLeft, drawTop: Integer;
drawWidth, drawHeight: Integer;
begin
inherited;
if fTexID = 0 then Exit; //No picture to draw
if ClipToBounds then
begin
TKMRenderUI.SetupClipX(AbsLeft, AbsLeft + Width);
TKMRenderUI.SetupClipY(AbsTop, AbsTop + Height);
end;
paintLightness := Lightness + HighlightCoef * (Byte(HighlightOnMouseOver and (csOver in State)) + Byte(Highlight));
if Tiled then
begin
drawWidth := gGFXData[fRX, fTexID].PxWidth;
drawHeight := gGFXData[fRX, fTexID].PxHeight;
drawLeft := AbsLeft + Width div 2 - drawWidth div 2;
drawTop := AbsTop + Height div 2 - drawHeight div 2;
col := Width div drawWidth + 1;
row := Height div drawHeight + 1;
for x := -col div 2 to col div 2 do
for y := -row div 2 to row div 2 do
TKMRenderUI.WritePicture(drawLeft + x * drawWidth, drawTop + y * drawHeight, drawWidth, drawHeight, ImageAnchors, fRX, fTexID, Enabled, fFlagColor, paintLightness);
end
else
TKMRenderUI.WritePicture(AbsLeft, AbsTop, Width, Height, ImageAnchors, fRX, fTexID, Enabled, fFlagColor, paintLightness);
if ClipToBounds then
begin
TKMRenderUI.ReleaseClipX;
TKMRenderUI.ReleaseClipY;
end;
end;
{ TKMImageStack }
constructor TKMImageStack.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aTexID1, aTexID2: Word; aRX: TRXType = rxGui);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
fRX := aRX;
fTexID1 := aTexID1;
fTexID2 := aTexID2;
end;
procedure TKMImageStack.SetCount(aCount, aColumns, aHighlightID: Word);
var
aspect: Single;
begin
fCount := aCount;
fColumns := Math.max(1, aColumns);
fHighlightID := aHighlightID;
fDrawWidth := EnsureRange(Width div fColumns, 8, gGFXData[fRX, fTexID1].PxWidth);
fDrawHeight := EnsureRange(Height div Ceil(fCount/fColumns), 6, gGFXData[fRX, fTexID1].PxHeight);
aspect := gGFXData[fRX, fTexID1].PxWidth / gGFXData[fRX, fTexID1].PxHeight;
if fDrawHeight * aspect <= fDrawWidth then
fDrawWidth := Round(fDrawHeight * aspect)
else
fDrawHeight := Round(fDrawWidth / aspect);
end;
// If image area is bigger than image - do center image in it
procedure TKMImageStack.Paint;
var
I: Integer;
offsetX, offsetY, centerX, centerY: SmallInt; //variable parameters
texID: Word;
begin
inherited;
if fTexID1 = 0 then Exit; //No picture to draw
offsetX := Width div fColumns;
offsetY := Height div Ceil(fCount / fColumns);
centerX := (Width - offsetX * (fColumns-1) - fDrawWidth) div 2;
centerY := (Height - offsetY * (Ceil(fCount/fColumns) - 1) - fDrawHeight) div 2;
for I := 0 to fCount - 1 do
begin
texID := IfThen(I = fHighlightID, fTexID2, fTexId1);
TKMRenderUI.WritePicture(AbsLeft + centerX + offsetX * (I mod fColumns),
AbsTop + centerY + offsetY * (I div fColumns),
fDrawWidth, fDrawHeight, [anLeft, anTop, anRight, anBottom], fRX, texID, Enabled);
end;
end;
{ TKMButton }
constructor TKMButton.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aTexID: Word; aRX: TRXType;
aStyle: TKMButtonStyle);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
InitCommon(aStyle);
fRX := aRX;
TexID := aTexID;
end;
{Different version of button, with caption on it instead of image}
constructor TKMButton.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; const aCaption: UnicodeString;
aStyle: TKMButtonStyle);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
InitCommon(aStyle);
Caption := aCaption;
end;
procedure TKMButton.InitCommon(aStyle: TKMButtonStyle);
begin
TexID := 0;
Caption := '';
FlagColor := $FFFF00FF;
Font := fntMetal;
fTextAlign := taCenter; //Thats default everywhere in KaM
TextVAlign := tvaMiddle;//tvaNone;
fStyle := aStyle;
MakesSound := True;
ShowImageEnabled := True;
AutoHeight := False;
AutoTextPadding := 5;
CapColor := icWhite;
end;
procedure TKMButton.UpdateHeight;
var
textY: Integer;
begin
if fAutoHeight then
begin
textY := gRes.Fonts[Font].GetTextSize(Caption).Y;
if textY + AutoTextPadding > Height then
Height := textY + AutoTextPadding;
end;
end;
procedure TKMButton.SetCaption(const aCaption: UnicodeString);
begin
fCaption := aCaption;
UpdateHeight;
end;
procedure TKMButton.SetAutoHeight(aValue: Boolean);
begin
fAutoHeight := aValue;
UpdateHeight;
end;
//DoClick is called by keyboard shortcuts
//It puts a focus on the button and depresses it if it was DoPress'ed
//It's important that Control must be:
// Visible (can't shortcut invisible/unaccessible button)
// Enabled (can't shortcut disabled function, e.g. Halt during fight)
function TKMButton.Click: Boolean;
begin
if Visible and Enabled then
begin
//Mark self as CtrlOver and CtrlUp, don't mark CtrlDown since MouseUp manually Nils it
Parent.MasterControl.CtrlOver := Self;
Parent.MasterControl.CtrlUp := Self;
if Assigned(OnClick) then
OnClick(Self);
Result := True; //Click has happened
end
else
Result := False; //No, we couldn't click for Control is unreachable
end;
procedure TKMButton.MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
if Enabled and MakesSound and (csDown in State) then
gSoundPlayer.Play(sfxnButtonClick);
inherited;
end;
procedure TKMButton.Paint;
var
col: TColor4;
stateSet: TKMButtonStateSet;
textY, top: Integer;
begin
inherited;
stateSet := [];
if (csOver in State) and Enabled then
stateSet := stateSet + [bsOver];
if (csOver in State) and (csDown in State) then
stateSet := stateSet + [bsDown];
if not Enabled then
stateSet := stateSet + [bsDisabled];
TKMRenderUI.Write3DButton(AbsLeft, AbsTop, Width, Height, fRX, TexID, FlagColor, stateSet, fStyle, ShowImageEnabled);
if TexID <> 0 then Exit;
//If disabled then text should be faded
col := IfThen(Enabled, CapColor, icGray);
top := AbsTop + Byte(bsDown in stateSet) + CapOffsetY;
textY := gRes.Fonts[Font].GetTextSize(Caption).Y;
case TextVAlign of
tvaNone: Inc(top, (Height div 2) - 7);
tvaTop: Inc(top, 2);
tvaMiddle: Inc(top, (Height div 2) - (textY div 2) + 2);
tvaBottom: Inc(top, Height - textY);
end;
TKMRenderUI.WriteText(AbsLeft + Byte(bsDown in stateSet) + CapOffsetX, top,
Width, Caption, Font, fTextAlign, col);
end;
{TKMButtonFlatCommon}
constructor TKMButtonFlatCommon.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight, aTexID: Integer; aRX: TRXType = rxGui);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
RX := aRX;
TexID := aTexID;
FlagColor := $FFFF00FF;
CapColor := $FFFFFFFF;
Font := fntGame;
Clickable := True;
EdgeAlpha := BEVEL_EDGE_ALPHA_DEF;
BackAlpha := BEVEL_BACK_ALPHA_DEF;
end;
procedure TKMButtonFlatCommon.MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
if not Clickable then Exit;
if Enabled and (csDown in State) then
gSoundPlayer.Play(sfxClick);
inherited;
end;
procedure TKMButtonFlatCommon.Paint;
begin
inherited;
TKMRenderUI.WriteBevel(AbsLeft, AbsTop, Width, Height, EdgeAlpha, BackAlpha);
if (csOver in State) and Enabled and not HideHighlight then
TKMRenderUI.WriteShape(AbsLeft+1, AbsTop+1, Width-2, Height-2, $40FFFFFF);
end;
//Simple version of button, with a caption and image
{TKMButtonFlat}
procedure TKMButtonFlat.Paint;
var
textCol: TColor4;
begin
inherited;
if TexID <> 0 then
TKMRenderUI.WritePicture(AbsLeft + TexOffsetX,
AbsTop + TexOffsetY - 6 * Byte(Caption <> ''),
Width, Height, [], RX, TexID, Enabled or fEnabledVisually, FlagColor);
textCol := IfThen(Enabled or fEnabledVisually, CapColor, icGray);
TKMRenderUI.WriteText(AbsLeft + CapOffsetX, AbsTop + (Height div 2) + 4 + CapOffsetY, Width, Caption, Font, taCenter, textCol);
if Down then
TKMRenderUI.WriteOutline(AbsLeft, AbsTop, Width, Height, 1, $FFFFFFFF);
{if not fEnabled then
TKMRenderUI.WriteShape(Left, Top, Width, Height, $80000000);}
end;
{ TKMFlatButtonShape }
constructor TKMFlatButtonShape.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; const aCaption: UnicodeString; aFont: TKMFont; aShapeColor: TColor4);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
fCaption := aCaption;
ShapeColor := aShapeColor;
fFont := aFont;
fFontHeight := gRes.Fonts[fFont].BaseHeight + 2;
FontColor := icWhite;
end;
procedure TKMFlatButtonShape.Paint;
begin
inherited;
TKMRenderUI.WriteBevel(AbsLeft, AbsTop, Width, Height);
//Shape within bevel
TKMRenderUI.WriteShape(AbsLeft + 1, AbsTop + 1, Width - 2, Width - 2, ShapeColor);
TKMRenderUI.WriteText(AbsLeft, AbsTop + (Height - fFontHeight) div 2,
Width, fCaption, fFont, taCenter, FontColor);
if (csOver in State) and Enabled then
TKMRenderUI.WriteShape(AbsLeft + 1, AbsTop + 1, Width - 2, Height - 2, $40FFFFFF);
if (csDown in State) or Down then
TKMRenderUI.WriteOutline(AbsLeft, AbsTop, Width, Height, 1, icWhite);
end;
end.
+519
View File
@@ -0,0 +1,519 @@
unit KM_ControlsChart;
{$I KaM_Remake.inc}
interface
uses
Classes, Controls,
KromOGLUtils,
KM_Controls,
KM_RenderUI, KM_ResFonts,
KM_CommonClasses, KM_CommonTypes, KM_Points, KM_Defaults;
type
TKMNotifyLegendClick = procedure(Sender: TObject; aLineIndex: Integer; aLineVisible: Boolean) of object;
TKMGraphLine = record
Title: UnicodeString;
TitleDetailed: TKMStringArray;
TitleDetailedColor: TKMCardinalArray;
Tag: Integer;
Color: TColor4;
Visible: Boolean;
Values: TKMCardinalArray;
ValuesAlt: TKMCardinalArray;
end;
TKMChart = class(TKMControl)
private
fCaption: UnicodeString;
fFont: TKMFont;
fCount: Integer;
fItemHeight: Byte;
fLegendWidth: Word;
fLegendCaption: String;
fLineOver: Integer;
fLines: array of TKMGraphLine;
fMaxLength: Cardinal; //Maximum samples (by horizontal axis)
fMinTime: Cardinal; //Minimum time (in sec), used only for Rendering time ticks
fMaxTime: Cardinal; //Maximum time (in sec), used only for Rendering time ticks
fMaxValue: Cardinal; //Maximum value (by vertical axis)
fPeaceTime: Cardinal;
//Legend separators
fSeparatorPositions: TXStringList;
fSeparatorHeight: Byte;
fSeparatorColor: TColor4;
fOnLegendClick: TKMNotifyLegendClick;
procedure UpdateMaxValue;
function GetLine(aIndex:Integer): TKMGraphLine;
function GetLineNumber(aY: Integer): Integer;
// function GetSeparatorsHeight(aIndex: Integer): Integer;
function GetSeparatorPos(aIndex: Integer): Integer;
public
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer);
destructor Destroy; override;
procedure AddLine(const aTitle: UnicodeString; aColor: TColor4; const aValues: TKMCardinalArray; aTag: Integer = -1); overload;
procedure AddLine(const aTitle: UnicodeString; aColor: TColor4; const aTitleDetailed: TKMStringArray;
const aTitleDetailedColor: TKMCardinalArray; const aValues: TKMCardinalArray; aTag: Integer = -1); overload;
procedure AddAltLine(const aAltValues: TKMCardinalArray);
procedure TrimToFirstVariation;
property Caption: UnicodeString read fCaption write fCaption;
procedure Clear;
procedure SetLineVisible(aLineID:Integer; aVisible:Boolean);
property MaxLength: Cardinal read fMaxLength write fMaxLength;
property MaxTime: Cardinal read fMaxTime write fMaxTime;
property Lines[aIndex: Integer]: TKMGraphLine read GetLine;
property LineCount:Integer read fCount;
property Font: TKMFont read fFont write fFont;
property LegendWidth: Word read fLegendWidth write fLegendWidth;
property LegendCaption: String read fLegendCaption write fLegendCaption;
property Peacetime: Cardinal read fPeaceTime write fPeaceTime;
property SeparatorPos[aIndex: Integer]: Integer read GetSeparatorPos;
property SeparatorColor: TColor4 read fSeparatorColor write fSeparatorColor;
property SeparatorHeight: Byte read fSeparatorHeight write fSeparatorHeight;
procedure AddSeparator(aPosition: Integer);
procedure SetSeparatorPositions(aSeparatorPositions: TStringList);
procedure ClearSeparators;
property OnLegendClick: TKMNotifyLegendClick read fOnLegendClick write fOnLegendClick;
procedure MouseMove(X,Y: Integer; Shift: TShiftState); override;
procedure MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
procedure Paint; override;
end;
implementation
uses
SysUtils, Math,
KM_Resource, KM_ResTexts,
KM_CommonUtils;
{ TKMChart }
constructor TKMChart.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
fSeparatorPositions := TXStringList.Create;
fSeparatorPositions.Sorted := True; // Better we have separators sorted
fFont := fntOutline;
fItemHeight := 20;
fLineOver := -1;
fLegendWidth := 150;
fSeparatorColor := clChartSeparator;
fSeparatorHeight := 10;
end;
destructor TKMChart.Destroy;
begin
FreeAndNil(fSeparatorPositions);
inherited;
end;
procedure TKMChart.AddLine(const aTitle: UnicodeString; aColor: TColor4; const aValues: TKMCardinalArray; aTag: Integer = -1);
var
titleDetailed: TKMStringArray;
titleDetailedColor: TKMCardinalArray;
begin
SetLength(titleDetailed, 0);
SetLength(titleDetailedColor, 0);
AddLine(aTitle, aColor, titleDetailed, titleDetailedColor, aValues, aTag);
end;
procedure TKMChart.AddLine(const aTitle: UnicodeString; aColor: TColor4; const aTitleDetailed: TKMStringArray;
const aTitleDetailedColor: TKMCardinalArray; const aValues: TKMCardinalArray; aTag: Integer = -1);
begin
if fMaxLength = 0 then Exit;
Assert(Length(aTitleDetailed) = Length(aTitleDetailedColor), 'aTitleDetailed and aTitleDetailedColor should have same length');
//Make sure there is enough Values to copy to local storage with Move procedure
Assert(Length(aValues) >= fMaxLength);
SetLength(fLines, fCount + 1);
fLines[fCount].Color := aColor;
fLines[fCount].Title := aTitle;
fLines[fCount].Tag := aTag;
fLines[fCount].Visible := True;
fLines[fCount].TitleDetailed := aTitleDetailed;
fLines[fCount].TitleDetailedColor := aTitleDetailedColor;
SetLength(fLines[fCount].Values, fMaxLength);
if SizeOf(aValues) <> 0 then
Move(aValues[0], fLines[fCount].Values[0], SizeOf(aValues[0]) * fMaxLength);
Inc(fCount);
UpdateMaxValue;
end;
function TKMChart.GetSeparatorPos(aIndex: Integer): Integer;
begin
Result := -1;
if not InRange(aIndex, 0, fSeparatorPositions.Count - 1) then Exit;
Result := StrToInt(fSeparatorPositions[aIndex]);
end;
//function TKMChart.GetSeparatorsHeight(aIndex: Integer): Integer;
//var
// I, Pos: Integer;
//begin
// Result := 0;
// for I := 0 to fSeparatorPositions.Count - 1 do
// begin
// Pos := SeparatorPos[I];
// if (Pos <> -1) and (Pos <= aIndex) then
// Inc(Result, fSeparatorHeight);
// end;
//end;
procedure TKMChart.AddSeparator(aPosition: Integer);
begin
fSeparatorPositions.Add(IntToStr(aPosition));
end;
procedure TKMChart.SetSeparatorPositions(aSeparatorPositions: TStringList);
begin
fSeparatorPositions.Clear;
fSeparatorPositions.AddStrings(aSeparatorPositions);
end;
//Add alternative values line (e.g. wares count vs. wares produced)
procedure TKMChart.AddAltLine(const aAltValues: TKMCardinalArray);
begin
Assert(Length(aAltValues) >= fMaxLength);
SetLength(fLines[fCount-1].ValuesAlt, fMaxLength);
if SizeOf(aAltValues) <> 0 then
Move(aAltValues[0], fLines[fCount-1].ValuesAlt[0], SizeOf(aAltValues[0]) * fMaxLength);
UpdateMaxValue;
end;
//Trims the graph until 5% before the first variation
procedure TKMChart.TrimToFirstVariation;
var
I, K, firstVarSample: Integer;
startVal: Cardinal;
begin
firstVarSample := -1;
for I:=0 to fCount-1 do
if Length(fLines[I].Values) > 0 then
begin
startVal := fLines[I].Values[0];
for K:=1 to Length(fLines[I].Values)-1 do
if fLines[I].Values[K] <> startVal then
begin
if (K < firstVarSample) or (firstVarSample = -1) then
firstVarSample := K - 1;
Break;
end;
end;
if firstVarSample <= 0 then
begin
fMinTime := 0; //No variation at all, so don't trim it (but clear previous value)
Exit;
end;
//Take 5% before the first varied sample
firstVarSample := Max(0, firstVarSample - Max(1, Round(0.05*(fMaxLength - firstVarSample))));
//Trim all fLines[I].Values to start at FirstVarSample
for I := 0 to fCount - 1 do
begin
Move(fLines[I].Values[firstVarSample], fLines[I].Values[0], (Length(fLines[I].Values)-firstVarSample)*SizeOf(fLines[I].Values[0]));
SetLength(fLines[I].Values, Length(fLines[I].Values)-firstVarSample);
end;
//Set start time so the horizontal time ticks are rendered correctly
fMinTime := Round((firstVarSample/fMaxLength) * fMaxTime);
//All lines have now been trimmed, so update fMaxLength
fMaxLength := fMaxLength - firstVarSample;
end;
procedure TKMChart.Clear;
begin
fCount := 0;
SetLength(fLines, 0);
fMaxValue := 0;
ClearSeparators;
end;
procedure TKMChart.ClearSeparators;
begin
fSeparatorPositions.Clear;
end;
procedure TKMChart.SetLineVisible(aLineID: Integer; aVisible: Boolean);
begin
fLines[aLineID].Visible := aVisible;
UpdateMaxValue;
end;
procedure TKMChart.UpdateMaxValue;
var
I, K: Integer;
begin
fMaxValue := 0;
for I := 0 to fCount - 1 do
if fLines[I].Visible then
for K := 0 to fMaxLength - 1 do
if fLines[I].Values[K] > fMaxValue then
fMaxValue := fLines[I].Values[K];
end;
function TKMChart.GetLine(aIndex: Integer): TKMGraphLine;
begin
Result := fLines[aIndex];
end;
function TKMChart.GetLineNumber(aY: Integer): Integer;
var
I, S, lineTop, lineBottom: Integer;
begin
Result := -1;
S := 0;
lineTop := AbsTop + 5 + 20*Byte(fLegendCaption <> '');
for I := 0 to fCount - 1 do
begin
if SeparatorPos[S] = I then
begin
Inc(lineTop, fSeparatorHeight);
Inc(S);
end;
lineBottom := lineTop + fItemHeight*(1 + Length(Lines[I].TitleDetailed));
if InRange(aY, lineTop, lineBottom) then
begin
Result := I;
Exit;
end;
lineTop := lineBottom;
end;
end;
procedure TKMChart.MouseMove(X, Y: Integer; Shift: TShiftState);
begin
inherited;
fLineOver := -1;
if X < AbsLeft + Width - fLegendWidth + 5 then Exit;
fLineOver := GetLineNumber(Y);
end;
procedure TKMChart.MouseUp(X, Y: Integer; Shift: TShiftState; Button: TMouseButton);
var
I: Integer;
begin
inherited;
if X < AbsLeft + Width - fLegendWidth+5 then Exit;
I := GetLineNumber(Y);
if not InRange(I, 0, fCount - 1) then Exit;
fLines[I].Visible := not fLines[I].Visible;
UpdateMaxValue;
if Assigned(fOnLegendClick) then
fOnLegendClick(Self, I, fLines[I].Visible);
end;
procedure TKMChart.Paint;
const
INTERVAL_CNT: array [0..9] of Word = (1, 5, 10, 50, 100, 500, 1000, 5000, 10000, 50000);
INTERVAL_TIME: array [0..10] of Word = (30, 1*60, 5*60, 10*60, 15*60, 30*60, 1*60*60, 2*60*60, 3*60*60, 4*60*60, 5*60*60);
var
G: TKMRect;
topValue: Integer;
procedure PaintAxisLabel(aTime: Integer; aIsPT: Boolean = False);
var
xPos: Integer;
begin
xPos := G.Left + Round((aTime - fMinTime) / (fMaxTime-fMinTime) * (G.Right - G.Left));
TKMRenderUI.WriteShape(xPos, G.Bottom - 2, 2, 5, IfThen(aIsPT, clChartPeacetimeLn, icWhite));
TKMRenderUI.WriteText (xPos, G.Bottom + 4, 0, TimeToString(aTime / 24 / 60 / 60), fntGame, taLeft, IfThen(aIsPT, clChartPeacetimeLbl, icWhite));
TKMRenderUI.WriteLine(xPos, G.Top, xPos, G.Bottom, IfThen(aIsPT, clChartPeacetimeLn, clChartDashedVLn), $CCCC);
if aIsPT then
TKMRenderUI.WriteText(xPos - 3, G.Bottom + 4, 0, gResTexts[TX_CHART_PT_END], fntGame, taRight, clChartPeacetimeLbl);
end;
procedure RenderHorizontalAxisTicks;
var
I, best: Integer;
begin
//Find first time interval that will have less than 10 ticks
best := 0;
for I := Low(INTERVAL_TIME) to High(INTERVAL_TIME) do
if (fMaxTime-fMinTime) div INTERVAL_TIME[I] < 7 then
begin
best := INTERVAL_TIME[I];
Break;
end;
//Paint time axis labels
if (best <> 0) and (fMaxTime <> fMinTime) then
if (fPeaceTime <> 0) and InRange(fPeaceTime, fMinTime, fMaxTime) then
begin
//Labels before PT and PT himself
for I := 0 to ((fPeaceTime - fMinTime) div best) do
PaintAxisLabel(fPeaceTime - I * best, I = 0);
//Labels after PT
for I := 1 to ((fMaxTime - fPeaceTime) div best) do
PaintAxisLabel(fPeaceTime + I * best);
end else
for I := Ceil(fMinTime / best) to (fMaxTime div best) do
PaintAxisLabel(I * best);
end;
function GetLineColor(aColor: Cardinal): Cardinal;
begin
//Adjust the color if it blends with black background
Result := EnsureBrightness(aColor, 0.3);
// If color is similar to highlight color, then use alternative HL color
if GetColorDistance(Result, clChartHighlight) < 0.1 then
Result := clChartHighlight2;
end;
procedure RenderChartAndLegend;
const
MARKS_FONT: TKMFont = fntGrey;
var
I, J, S, checkSize, xPos, yPos, height: Integer;
titleDetailedH: Integer;
newColor: TColor4;
begin
checkSize := gRes.Fonts[MARKS_FONT].GetTextSize('v').Y + 1;
S := 0;
xPos := G.Right + 10;
yPos := G.Top + 8 + 20*Byte(fLegendCaption <> '');
titleDetailedH := 0;
//Charts and legend
for I := 0 to fCount - 1 do
begin
newColor := GetLineColor(fLines[I].Color);
if (csOver in State) and (I = fLineOver) then
newColor := clChartHighlight;
//Charts
if fLines[I].Visible then
begin
TKMRenderUI.WritePlot(G.Left, G.Top, G.Right-G.Left, G.Bottom-G.Top, fLines[I].Values, topValue, newColor, 2);
if Length(fLines[I].ValuesAlt) > 0 then
TKMRenderUI.WritePlot(G.Left, G.Top, G.Right-G.Left, G.Bottom-G.Top, fLines[I].ValuesAlt, topValue, newColor, 1);
end;
if SeparatorPos[S] = I then
begin
Inc(yPos, fSeparatorHeight);
Inc(S);
end;
//Checkboxes
TKMRenderUI.WriteBevel(xPos, yPos, checkSize - 4, checkSize - 4, 1, 0.3);
TKMRenderUI.WriteOutline(xPos, yPos, checkSize - 4, checkSize - 4, 1, clChkboxOutline);
if fLines[I].Visible then
TKMRenderUI.WriteText(xPos + (checkSize-4) div 2, yPos - 1, 0, 'v', MARKS_FONT, taCenter, newColor);
//Legend
TKMRenderUI.WriteText(xPos + checkSize, yPos, 0, fLines[I].Title, fntGame, taLeft, newColor);
Inc(yPos, fItemHeight);
//Detailed legend
for J := Low(fLines[I].TitleDetailed) to High(fLines[I].TitleDetailed) do
begin
TKMRenderUI.WriteText(xPos + checkSize + 5, yPos, 0, fLines[I].TitleDetailed[J], fntGrey, taLeft, GetLineColor(fLines[I].TitleDetailedColor[J]));
Inc(yPos, fItemHeight);
Inc(titleDetailedH, fItemHeight);
end;
end;
//Legend title and outline
height := fItemHeight*fCount + titleDetailedH + 6 + 20*Byte(fLegendCaption <> '') + fSeparatorPositions.Count*fSeparatorHeight;
TKMRenderUI.WriteShape(G.Right + 5, G.Top, fLegendWidth, height, icDarkestGrayTrans);
TKMRenderUI.WriteOutline(G.Right + 5, G.Top, fLegendWidth, height, 1, icGray);
if fLegendCaption <> '' then
TKMRenderUI.WriteText(G.Right + 5, G.Top + 4, fLegendWidth, fLegendCaption, fntMetal, taCenter, icWhite);
end;
var
I: Integer;
best, tmp: Integer;
begin
inherited;
G := KMRect(AbsLeft + 40, AbsTop, AbsLeft + Width - fLegendWidth, AbsTop + Height - 20);
//Add margin to MaxValue so that it does not blends with upper border
topValue := Max(Round(fMaxValue * 1.1), fMaxValue + 1);
//Find first interval that will have less than 10 ticks
best := 0;
for I := Low(INTERVAL_CNT) to High(INTERVAL_CNT) do
if topValue div INTERVAL_CNT[I] < 10 then
begin
best := INTERVAL_CNT[I];
Break;
end;
//Dashed lines in the background
if best <> 0 then
for I := 1 to (topValue div best) do
begin
tmp := G.Top + Round((1 - I * best / topValue) * (G.Bottom - G.Top));
TKMRenderUI.WriteText(G.Left - 5, tmp - 6, 0, IntToStr(I * best), fntGame, taRight);
TKMRenderUI.WriteLine(G.Left, tmp, G.Right, tmp, clChartDashedHLn, $CCCC);
end;
//Render horizontal axis ticks
RenderHorizontalAxisTicks;
RenderChartAndLegend;
//Render the highlighted line above all the others and thicker so you can see where it goes under others
if (csOver in State) and InRange(fLineOver, 0, fCount-1) and fLines[fLineOver].Visible then
TKMRenderUI.WritePlot(G.Left, G.Top, G.Right-G.Left, G.Bottom-G.Top, fLines[fLineOver].Values, topValue, clChartHighlight, 3);
//Outline
TKMRenderUI.WriteOutline(G.Left, G.Top, G.Right-G.Left, G.Bottom-G.Top, 1, icWhite);
//Title
TKMRenderUI.WriteText(G.Left + 5, G.Top + 5, 0, fCaption, fFont, taLeft);
//Render vertical axis captions
TKMRenderUI.WriteText(G.Left - 5, G.Bottom - 6, 0, IntToStr(0), fntGame, taRight);
//TKMRenderUI.WriteText(Left+20, Top + 20, 0, 0, IntToStr(fMaxValue), fntGame, taRight);
end;
end.
+152
View File
@@ -0,0 +1,152 @@
unit KM_ControlsColor;
{$I KaM_Remake.inc}
interface
uses
Classes, Controls,
KromOGLUtils,
KM_Controls, KM_ControlsBase,
KM_CommonTypes;
type
// Color swatch - to select a color from given samples/palette
TKMColorSwatch = class(TKMControl)
private
fBackAlpha: single; // Alpha of background (usually 0.5, dropbox 1)
fCellSize: Byte; // Size of the square in pixels
fColumnCount: Byte;
fRowCount: Byte;
fColorIndex: Integer;
Colors: array of TColor4;
fOnChange: TNotifyEvent;
fInclRandom: Boolean;
public
constructor Create(aParent: TKMPanel; aLeft,aTop,aColumnCount,aRowCount,aSize: Integer);
procedure SetColors(const aColors: array of TColor4; aInclRandom: Boolean = False);
procedure SelectByColor(aColor: TColor4);
property BackAlpha: single read fBackAlpha write fBackAlpha;
property ColorIndex: Integer read fColorIndex write fColorIndex;
function GetColor: TColor4;
procedure MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
property OnChange: TNotifyEvent write fOnChange;
procedure Paint; override;
end;
implementation
uses
Math,
KM_RenderUI,
KM_ResFonts, KM_ResTypes,
KM_CommonUtils;
{ TKMColorSwatch }
constructor TKMColorSwatch.Create(aParent: TKMPanel; aLeft,aTop,aColumnCount,aRowCount,aSize: Integer);
begin
inherited Create(aParent, aLeft, aTop, 0, 0);
fBackAlpha := 0.5;
fColumnCount := aColumnCount;
fRowCount := aRowCount;
fCellSize := aSize;
fInclRandom := False;
fColorIndex := -1;
Width := fColumnCount * fCellSize;
Height := fRowCount * fCellSize;
end;
procedure TKMColorSwatch.SetColors(const aColors: array of TColor4; aInclRandom: Boolean = False);
begin
fInclRandom := aInclRandom;
if fInclRandom then
begin
SetLength(Colors, Length(aColors)+SizeOf(TColor4));
Colors[0] := $00000000; //This one is reserved for random
Move((@aColors[0])^, (@Colors[1])^, SizeOf(aColors));
end
else
begin
SetLength(Colors, Length(aColors));
Move((@aColors[0])^, (@Colors[0])^, SizeOf(aColors));
end;
end;
procedure TKMColorSwatch.SelectByColor(aColor: TColor4);
var
I: Integer;
begin
fColorIndex := -1;
for I:=0 to Length(Colors)-1 do
if Colors[I] = aColor then
fColorIndex := I;
end;
function TKMColorSwatch.GetColor: TColor4;
begin
if fColorIndex <> -1 then
Result := Colors[fColorIndex]
else
Result := $FF000000; //Black by default
end;
procedure TKMColorSwatch.MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
var
newColor: Integer;
begin
if Button = mbLeft then
begin
newColor := EnsureRange((Y-AbsTop) div fCellSize, 0, fRowCount-1)*fColumnCount +
EnsureRange((X-AbsLeft) div fCellSize, 0, fColumnCount-1);
if InRange(newColor, 0, Length(Colors)-1) then
begin
fColorIndex := newColor;
if Assigned(fOnChange) then fOnChange(Self);
end;
end;
inherited;
end;
procedure TKMColorSwatch.Paint;
var
I, start: Integer;
selColor: TColor4;
begin
inherited;
TKMRenderUI.WriteBevel(AbsLeft, AbsTop, Width, Height, 1, fBackAlpha);
start := 0;
if fInclRandom then
begin
//Render miniature copy of all available colors with '?' on top
for I := 0 to Length(Colors) - 1 do
TKMRenderUI.WriteShape(AbsLeft+(I mod fColumnCount)*(fCellSize div fColumnCount)+2, AbsTop+(I div fColumnCount)*(fCellSize div fColumnCount)+2, (fCellSize div fColumnCount), (fCellSize div fColumnCount), Colors[I]);
TKMRenderUI.WriteText(AbsLeft + fCellSize div 2, AbsTop + fCellSize div 4, 0, '?', fntMetal, taCenter);
start := 1;
end;
for I := start to Length(Colors) - 1 do
TKMRenderUI.WriteShape(AbsLeft+(I mod fColumnCount)*fCellSize, AbsTop+(I div fColumnCount)*fCellSize, fCellSize, fCellSize, Colors[I]);
if fColorIndex < 0 then Exit;
if GetColorBrightness(Colors[fColorIndex]) >= 0.5 then
selColor := $FF000000
else
selColor := $FFFFFFFF;
//Paint selection
TKMRenderUI.WriteOutline(AbsLeft+(fColorIndex mod fColumnCount)*fCellSize, AbsTop+(fColorIndex div fColumnCount)*fCellSize, fCellSize, fCellSize, 1, selColor);
end;
end.
+109
View File
@@ -0,0 +1,109 @@
unit KM_ControlsDragger;
{$I KaM_Remake.inc}
interface
uses
Classes, Controls,
KM_Controls;
type
//Element that player can drag within allowed bounds
TKMDragger = class(TKMControl)
private
fMinusX, fMinusY, fPlusX, fPlusY: Integer; //Restrictions
fPositionX: Integer;
fPositionY: Integer;
fStartDragX: Integer;
fStartDragY: Integer;
public
OnMove: TKMNotifyEventXY;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer);
procedure SetBounds(aMinusX, aMinusY, aPlusX, aPlusY: Integer);
procedure MouseDown(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
procedure MouseMove(X,Y: Integer; Shift: TShiftState); override;
procedure MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
procedure Paint; override;
end;
implementation
uses
Math,
KM_RenderUI,
KM_ResTypes;
{ TKMDragger }
constructor TKMDragger.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
//Original position is used to resrict movement
fPositionX := 0;
fPositionY := 0;
end;
procedure TKMDragger.SetBounds(aMinusX, aMinusY, aPlusX, aPlusY: Integer);
begin
fMinusX := aMinusX;
fMinusY := aMinusY;
fPlusX := aPlusX;
fPlusY := aPlusY;
end;
procedure TKMDragger.MouseDown(X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
inherited;
fStartDragX := X - fPositionX;
fStartDragY := Y - fPositionY;
MouseMove(X,Y,Shift);
end;
procedure TKMDragger.MouseMove(X,Y: Integer; Shift: TShiftState);
begin
inherited;
if csDown in State then
begin
//Bounds are signed numbers, set them properly
fPositionX := EnsureRange((X - fStartDragX), fMinusX, fPlusX);
fPositionY := EnsureRange((Y - fStartDragY), fMinusY, fPlusY);
if Assigned(OnMove) then OnMove(Self, fPositionX, fPositionY);
end;
end;
procedure TKMDragger.MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
inherited;
MouseMove(X,Y,Shift);
end;
procedure TKMDragger.Paint;
var
stateSet: TKMButtonStateSet;
begin
inherited;
stateSet := [];
if (csOver in State) and Enabled then
stateSet := stateSet + [bsOver];
if (csDown in State) then
stateSet := stateSet + [bsDown];
if not Enabled then
stateSet := stateSet + [bsDisabled];
TKMRenderUI.Write3DButton(AbsLeft, AbsTop, Width, Height, rxGui, 0, $FFFF00FF, stateSet, bsGame);
end;
end.
+792
View File
@@ -0,0 +1,792 @@
unit KM_ControlsDrop;
interface
uses
Classes,
Controls,
KromOGLUtils,
KM_Controls, KM_ControlsBase, KM_ControlsList,
KM_ResTypes, KM_ResFonts,
KM_RenderUI;
type
TKMDropCommon = class(TKMControl)
private
fDropCount: Integer;
fDropUp: Boolean;
fFont: TKMFont;
fButton: TKMButton;
fShape: TKMShape;
fAutoClose: Boolean;
fOnChange: TNotifyEvent;
fOnShowList: TNotifyEvent;
procedure UpdateDropPosition; virtual; abstract;
procedure ButtonClick(Sender: TObject);
procedure ListShow(Sender: TObject); virtual;
procedure ListClick(Sender: TObject); virtual;
procedure ListChange(Sender: TObject); virtual;
procedure ListHide(Sender: TObject); virtual;
function ListVisible: Boolean; virtual; abstract;
function GetItemIndex: SmallInt; virtual; abstract;
procedure SetItemIndex(aIndex: SmallInt); virtual; abstract;
protected
procedure DoClick(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
procedure SetLeft(aValue: Integer); override;
procedure SetTop(aValue: Integer); override;
procedure SetEnabled(aValue: Boolean); override;
procedure SetVisible(aValue: Boolean); override;
function ListKeyDown(Sender: TObject; Key: Word; Shift: TShiftState): Boolean;
procedure UpdateVisibility; override;
function GetCount: Integer; virtual; abstract;
procedure SetDropCount(const aValue: Integer); virtual;
public
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont; aStyle: TKMButtonStyle;
aAutoClose: Boolean = True);
procedure Clear; virtual; abstract;
procedure OpenList;
procedure CloseList;
procedure SetOpenList(aOpen: Boolean);
procedure SwitchOpen;
property DropCount: Integer read fDropCount write SetDropCount;
property DropUp: Boolean read fDropUp write fDropUp;
property ItemIndex: SmallInt read GetItemIndex write SetItemIndex;
function IsOpen: Boolean; virtual;
property Count: Integer read GetCount;
property OnShowList: TNotifyEvent read fOnShowList write fOnShowList;
property OnChange: TNotifyEvent read fOnChange write fOnChange;
procedure Paint; override;
end;
//DropBox with a ListBox
TKMDropList = class(TKMDropCommon)
private
fCaption: UnicodeString; //Current caption (Default or from list)
fDefaultCaption: UnicodeString;
fDropWidth: Integer;
fList: TKMListBox;
fListTopIndex: Integer;
procedure UpdateDropPosition; override;
procedure ListShow(Sender: TObject); override;
procedure ListClick(Sender: TObject); override;
procedure ListChange(Sender: TObject); override;
procedure ListHide(Sender: TObject); override;
function ListVisible: Boolean; override;
function GetItem(aIndex: Integer): UnicodeString;
function GetItemIndex: smallint; override;
procedure SetItemIndex(aIndex: smallint); override;
procedure SetDropWidth(aDropWidth: Integer);
function GetShowHintWhenShort: Boolean;
procedure SetShowHintWhenShort(const aValue: Boolean);
protected
procedure SetEnabled(aValue: Boolean); override;
procedure SetVisible(aValue: Boolean); override;
function GetCount: Integer; override;
procedure SetDropCount(const aValue: Integer); override;
public
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont; const aDefaultCaption: UnicodeString;
aStyle: TKMButtonStyle; aAutoClose: Boolean = True; aBackAlpha: Single = 0.85);
procedure Clear; override;
procedure Add(const aItem: UnicodeString; aTag: Integer = 0);
procedure SelectByName(const aText: UnicodeString);
procedure SelectByTag(aTag: Integer);
function HasTag(aTag: Integer): Boolean;
function GetTag(aIndex: Integer): Integer;
function GetSelectedTag: Integer;
function IsSelected: Boolean;
property ShowHintWhenShort: Boolean read GetShowHintWhenShort write SetShowHintWhenShort;
property DefaultCaption: UnicodeString read fDefaultCaption write fDefaultCaption;
property Item[aIndex: Integer]: UnicodeString read GetItem;
property List: TKMListBox read fList;
function IsOpen: Boolean; override;
property DropWidth: Integer read fDropWidth write SetDropWidth;
procedure Paint; override;
end;
//DropBox with a ColumnBox
TKMDropColumns = class(TKMDropCommon)
private
fDefaultCaption: UnicodeString;
fDropWidth: Integer;
fList: TKMColumnBox;
fListTopIndex: Integer;
fColumnsToShowWhenListHidden: array of Boolean; //which columns to show, when list is hidden
procedure UpdateDropPosition; override;
procedure ListShow(Sender: TObject); override;
procedure ListClick(Sender: TObject); override;
procedure ListChange(Sender: TObject); override;
procedure ListHide(Sender: TObject); override;
function ListVisible: Boolean; override;
function GetItem(aIndex: Integer): TKMListRow;
function GetItemIndex: Smallint; override;
procedure SetItemIndex(aIndex: Smallint); override;
procedure SetDropWidth(aDropWidth: Integer);
function GetShowHintWhenShort: Boolean;
procedure SetShowHintWhenShort(const aValue: Boolean);
protected
procedure SetEnabled(aValue: Boolean); override;
procedure SetVisible(aValue: Boolean); override;
function GetCount: Integer; override;
public
FadeImageWhenDisabled: Boolean;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont; const aDefaultCaption: UnicodeString; aStyle: TKMButtonStyle; aShowHeader: Boolean = True);
procedure Add(aItem: TKMListRow);
procedure Clear; override;
property List: TKMColumnBox read fList;
property Item[aIndex: Integer]: TKMListRow read GetItem; default;
procedure SetColumns(aFont: TKMFont; aColumns: array of string; aColumnOffsets: array of Word); overload;
procedure SetColumns(aFont: TKMFont; aColumns: array of string; aColumnOffsets: array of Word; aColumnsToShowWhenListHidden: array of Boolean); overload;
property DefaultCaption: UnicodeString read fDefaultCaption write fDefaultCaption;
property DropWidth: Integer read fDropWidth write SetDropWidth;
property ShowHintWhenShort: Boolean read GetShowHintWhenShort write SetShowHintWhenShort;
procedure Paint; override;
end;
implementation
uses
{$IFDEF MSWindows} Windows, {$ENDIF}
{$IFDEF Unix} LCLIntf, LCLType, {$ENDIF}
Math,
KM_Defaults;
{ TKMDropCommon }
constructor TKMDropCommon.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont;
aStyle: TKMButtonStyle; aAutoClose: Boolean = True);
var
P: TKMPanel;
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
fDropCount := 10;
fDropUp := False;
fFont := aFont;
fButton := TKMButton.Create(aParent, aLeft+aWidth-aHeight, aTop, aHeight, aHeight, 590, rxGui, aStyle);
fButton.OnClick := ButtonClick;
fButton.MakesSound := False;
P := MasterParent;
fShape := TKMShape.Create(P, 0, 0, P.Width, P.Height);
fShape.AnchorsStretch;
fShape.OnClick := ListHide;
fAutoClose := aAutoClose;
end;
procedure TKMDropCommon.UpdateVisibility;
begin
inherited;
if not Visible then
CloseList;
end;
function TKMDropCommon.IsOpen: Boolean;
begin
Result := fShape.Visible;
end;
procedure TKMDropCommon.ButtonClick(Sender: TObject);
begin
//Call the DoDlick event to show the list AND generate DropBox.OnClick event
DoClick(fButton.AbsLeft + fButton.Width div 2, fButton.AbsTop + fButton.Height div 2, [], mbLeft);
end;
procedure TKMDropCommon.ListShow(Sender: TObject);
begin
if ListVisible then
begin
ListHide(nil);
Exit;
end;
if fAutoClose and (Count > 0) then
fShape.Show;
if Assigned(fOnShowList) then fOnShowList(Self);
end;
procedure TKMDropCommon.DoClick(X, Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
//do not invoke inherited here, to fully override parent DoClick method
//It's common behavior when click on dropbox will show the list
if fAutoClose then
ListShow(Self)
else
if not ListVisible then
begin
fButton.TexId := 591;
ListShow(Self)
end else begin
fButton.TexId := 590;
ListHide(Self);
end;
inherited;
end;
//Handle KeyDown on List
function TKMDropCommon.ListKeyDown(Sender: TObject; Key: Word; Shift: TShiftState): Boolean;
begin
if fAutoClose and (Key = VK_ESCAPE) then // Close List on ESC, if autoclosable
begin
ListHide(nil);
Result := True;
end else
Result := False;
end;
procedure TKMDropCommon.ListClick(Sender: TObject);
begin
//No need to call fOnChange here since ListChange was already called
if fAutoClose then ListHide(nil);
end;
procedure TKMDropCommon.ListChange(Sender: TObject);
begin
if (ItemIndex <> -1) then
if Assigned(fOnChange) then fOnChange(Self);
end;
procedure TKMDropCommon.ListHide(Sender: TObject);
begin
fShape.Hide;
end;
procedure TKMDropCommon.SetDropCount(const aValue: Integer);
begin
fDropCount := aValue;
end;
procedure TKMDropCommon.SetEnabled(aValue: Boolean);
begin
inherited;
fButton.Enabled := Enabled;
end;
procedure TKMDropCommon.OpenList;
begin
ListShow(nil);
end;
procedure TKMDropCommon.CloseList;
begin
ListHide(nil);
end;
procedure TKMDropCommon.SetOpenList(aOpen: Boolean);
begin
if aOpen then
OpenList
else
CloseList;
end;
procedure TKMDropCommon.SwitchOpen;
begin
if IsOpen then
CloseList
else
OpenList;
end;
procedure TKMDropCommon.SetLeft(aValue: Integer);
begin
inherited;
// Stick the button to us
fButton.Left := Left + Width - Height;
end;
procedure TKMDropCommon.SetTop(aValue: Integer);
begin
inherited;
// Stick the button to us
fButton.Top := Top;
end;
procedure TKMDropCommon.SetVisible(aValue: Boolean);
begin
inherited;
fButton.Visible := aValue;
if not aValue then ListHide(Self);
end;
procedure TKMDropCommon.Paint;
begin
inherited;
TKMRenderUI.WriteBevel(AbsLeft, AbsTop, Width, Height);
// Make sure the list stays where it needs to be relative DropBox (e.g. on window resize)
UpdateDropPosition;
end;
{ TKMDropList }
constructor TKMDropList.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont; const aDefaultCaption: UnicodeString;
aStyle: TKMButtonStyle; aAutoClose: Boolean = True; aBackAlpha: Single = 0.85);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight, aFont, aStyle, aAutoClose);
fDefaultCaption := aDefaultCaption;
fListTopIndex := 0;
fList := TKMListBox.Create(MasterParent, 0, 0, aWidth, 0, fFont, aStyle);
fList.Height := fList.ItemHeight * fDropCount;
fList.AutoHideScrollBar := True; //A drop box should only have a scrollbar if required
fList.BackAlpha := aBackAlpha;
fList.OnClick := ListClick;
fList.OnChange := ListChange;
DropWidth := aWidth;
ListHide(nil);
fList.OnKeyDown := ListKeyDown;
end;
procedure TKMDropList.ListShow(Sender: TObject);
begin
inherited;
if ListVisible or (Count < 1) then Exit;
//Make sure the selected item is visible when list is opened
if (ItemIndex <> -1) then
begin
if InRange(ItemIndex, fListTopIndex, fListTopIndex + fList.GetVisibleRows) then //Try to open list at previously saved scroll position
fList.TopIndex := fListTopIndex
else
fList.TopIndex := ItemIndex;
end;
fList.Show;
end;
procedure TKMDropList.ListClick(Sender: TObject);
begin
fCaption := fList.Item[ItemIndex];
inherited;
end;
procedure TKMDropList.ListChange(Sender: TObject);
begin
fCaption := fList.Item[ItemIndex];
inherited;
end;
procedure TKMDropList.ListHide(Sender: TObject);
begin
fListTopIndex := fList.TopIndex; //Save list scroll position
inherited;
fList.Hide;
end;
function TKMDropList.ListVisible: Boolean;
begin
Result := fList.Visible;
end;
function TKMDropList.GetItemIndex: Smallint;
begin
Result := fList.ItemIndex;
end;
procedure TKMDropList.SetItemIndex(aIndex: Smallint);
begin
fList.ItemIndex := aIndex;
if aIndex <> -1 then
fCaption := fList.Item[fList.ItemIndex]
else
fCaption := fDefaultCaption;
end;
function TKMDropList.GetShowHintWhenShort: Boolean;
begin
Result := fList.ShowHintWhenShort;
end;
procedure TKMDropList.SetShowHintWhenShort(const aValue: Boolean);
begin
fList.ShowHintWhenShort := aValue;
end;
function TKMDropList.IsOpen: Boolean;
begin
Result := fList.Visible;
end;
procedure TKMDropList.SetDropCount(const aValue: Integer);
begin
inherited;
fList.Height := fList.ItemHeight * fDropCount;
end;
procedure TKMDropList.SetDropWidth(aDropWidth: Integer);
begin
fDropWidth := aDropWidth;
fList.AbsLeft := AbsLeft + Width - aDropWidth;
fList.Width := aDropWidth;
end;
procedure TKMDropList.SetEnabled(aValue: Boolean);
begin
inherited;
fList.Enabled := Enabled;
if not Enabled and fList.Visible then
ListHide(nil);
end;
procedure TKMDropList.SetVisible(aValue: Boolean);
begin
inherited;
if not aValue then ListHide(Self);
end;
function TKMDropList.GetCount: Integer;
begin
Result := fList.Count;
end;
// When new items are added to the list we must update the drop height and position
procedure TKMDropList.UpdateDropPosition;
begin
if Count > 0 then
begin
fList.Height := Math.Min(fDropCount, fList.Count) * fList.ItemHeight + fList.SeparatorsCount*fList.SeparatorHeight;
if fDropUp then
fList.AbsTop := AbsTop - fList.Height
else
fList.AbsTop := AbsTop + Height;
fList.Left := AbsLeft + Width - DropWidth - MasterParent.AbsLeft;
end;
end;
procedure TKMDropList.Add(const aItem: UnicodeString; aTag: Integer = 0);
begin
fList.Add(aItem, aTag);
end;
procedure TKMDropList.SelectByName(const aText: UnicodeString);
var I: Integer;
begin
fList.ItemIndex := -1;
for I := 0 to fList.Count - 1 do
if fList.Item[I] = aText then
SetItemIndex(I);
end;
procedure TKMDropList.SelectByTag(aTag: Integer);
var I: Integer;
begin
fList.ItemIndex := -1;
for I := 0 to fList.Count - 1 do
if fList.ItemTags[I] = aTag then
SetItemIndex(I);
end;
function TKMDropList.HasTag(aTag: Integer): Boolean;
var
I: Integer;
begin
Result := False;
for I := 0 to fList.Count - 1 do
if fList.ItemTags[I] = aTag then
Exit(True);
end;
function TKMDropList.IsSelected: Boolean;
begin
Result := fList.ItemIndex <> -1;
end;
function TKMDropList.GetTag(aIndex: Integer): Integer;
begin
Result := fList.ItemTags[aIndex];
end;
function TKMDropList.GetSelectedTag: Integer;
begin
Result := GetTag(fList.ItemIndex);
end;
function TKMDropList.GetItem(aIndex: Integer): UnicodeString;
begin
Result := fList.Item[aIndex];
end;
procedure TKMDropList.Clear;
begin
fList.Clear;
end;
procedure TKMDropList.Paint;
var
col: TColor4;
begin
inherited;
if Enabled then
col := icWhite
else
col := icGray2;
TKMRenderUI.WriteText(AbsLeft+4, AbsTop+4, Width-8, fCaption, fFont, taLeft, col);
end;
{ TKMDropColumns }
constructor TKMDropColumns.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont; const aDefaultCaption: UnicodeString; aStyle: TKMButtonStyle; aShowHeader: Boolean = True);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight, aFont, aStyle);
fListTopIndex := 0;
fDefaultCaption := aDefaultCaption;
fList := TKMColumnBox.Create(MasterParent, 0, 0, aWidth, 0, fFont, aStyle);
fList.BackAlpha := 0.85;
fList.OnClick := ListClick;
fList.OnChange := ListChange;
fList.ShowHeader := aShowHeader;
DropWidth := aWidth;
ListHide(nil);
fList.OnKeyDown := ListKeyDown;
end;
procedure TKMDropColumns.ListShow(Sender: TObject);
begin
inherited;
if ListVisible or (Count < 1) then Exit;
//Make sure the selected item is visible when list is opened
if (ItemIndex <> -1) then
begin
if InRange(ItemIndex, fListTopIndex, fListTopIndex + fList.GetVisibleRows) then //Try to open list at previously saved scroll position
fList.TopIndex := fListTopIndex
else
fList.TopIndex := ItemIndex;
end;
fList.Show;
end;
procedure TKMDropColumns.ListClick(Sender: TObject);
begin
inherited;
end;
procedure TKMDropColumns.ListChange(Sender: TObject);
begin
inherited;
end;
procedure TKMDropColumns.ListHide(Sender: TObject);
begin
fListTopIndex := fList.TopIndex; //Save list scroll position
inherited;
fList.Hide;
end;
function TKMDropColumns.ListVisible: Boolean;
begin
Result := fList.Visible;
end;
function TKMDropColumns.GetItemIndex: smallint;
begin
Result := fList.ItemIndex;
end;
procedure TKMDropColumns.SetItemIndex(aIndex: smallint);
begin
fList.ItemIndex := aIndex;
end;
procedure TKMDropColumns.SetDropWidth(aDropWidth: Integer);
begin
fDropWidth := aDropWidth;
fList.AbsLeft := AbsLeft + Width - aDropWidth;
fList.Width := aDropWidth;
end;
function TKMDropColumns.GetShowHintWhenShort: Boolean;
begin
Result := fList.ShowHintWhenShort
end;
procedure TKMDropColumns.SetShowHintWhenShort(const aValue: Boolean);
begin
fList.ShowHintWhenShort := aValue;
end;
procedure TKMDropColumns.SetColumns(aFont: TKMFont; aColumns: array of string; aColumnOffsets: array of Word);
var
I: Integer;
columnsToShowWhenListHidden: array of Boolean;
begin
SetLength(columnsToShowWhenListHidden, Length(aColumns));
for I := Low(columnsToShowWhenListHidden) to High(columnsToShowWhenListHidden) do
columnsToShowWhenListHidden[I] := True; // by default show all columns
SetColumns(aFont, aColumns, aColumnOffsets, columnsToShowWhenListHidden);
end;
procedure TKMDropColumns.SetColumns(aFont: TKMFont; aColumns: array of string; aColumnOffsets: array of Word; aColumnsToShowWhenListHidden: array of Boolean);
var
I: Integer;
begin
Assert(Length(aColumns) = Length(aColumnsToShowWhenListHidden));
fList.SetColumns(aFont, aColumns, aColumnOffsets);
SetLength(fColumnsToShowWhenListHidden, Length(aColumnsToShowWhenListHidden));
for I := Low(aColumnsToShowWhenListHidden) to High(aColumnsToShowWhenListHidden) do
fColumnsToShowWhenListHidden[I] := aColumnsToShowWhenListHidden[I];
end;
procedure TKMDropColumns.SetEnabled(aValue: Boolean);
begin
inherited;
fList.Enabled := Enabled;
end;
procedure TKMDropColumns.SetVisible(aValue: Boolean);
begin
inherited;
if not aValue then ListHide(Self);
end;
function TKMDropColumns.GetCount: Integer;
begin
Result := fList.RowCount;
end;
//When new items are added to the list we must update the drop height and position
procedure TKMDropColumns.UpdateDropPosition;
begin
if Count > 0 then
begin
fList.Height := Math.Min(fDropCount, fList.RowCount) * fList.ItemHeight + fList.Header.Height * Ord(fList.ShowHeader);
if fDropUp then
fList.AbsTop := AbsTop - fList.Height
else
fList.AbsTop := AbsTop + Height;
fList.Left := AbsLeft + Width - DropWidth - MasterParent.AbsLeft;
end;
end;
procedure TKMDropColumns.Add(aItem: TKMListRow);
begin
fList.AddItem(aItem);
end;
function TKMDropColumns.GetItem(aIndex: Integer): TKMListRow;
begin
Result := fList.Rows[aIndex];
end;
procedure TKMDropColumns.Clear;
begin
fList.Clear;
end;
procedure TKMDropColumns.Paint;
var
col: TColor4;
begin
inherited;
if Enabled then
col := icWhite
else
col := icGray2;
if ItemIndex = -1 then
TKMRenderUI.WriteText(AbsLeft + 4, AbsTop + 4, Width - 8 - fButton.Width, fDefaultCaption, fFont, taLeft, col)
else
fList.DoPaintLine(ItemIndex, AbsLeft, AbsTop, Width - fButton.Width, fColumnsToShowWhenListHidden, False);
end;
end.
File diff suppressed because it is too large. Load diff
+350
View File
@@ -0,0 +1,350 @@
unit KM_ControlsForm;
{$I KaM_Remake.inc}
interface
uses
Classes, Controls,
KM_Controls, KM_ControlsBase,
KM_CommonTypes, KM_Points, KM_ResFonts;
type
TKMFormBackgroundType = (
fbGray, // Dark grey scroll with big header roll
fbYellow, // Yellow scroll without header roll
fbScroll // Yellow scroll with small header roll
);
TKMForm = class(TKMPanel)
const
DEFAULT_CAPTION_FONT = fntOutline;
private
fDragging: Boolean;
fDragStartPos: TKMPoint;
fBackground: TKMFormBackgroundType;
fHandleCloseKey: Boolean;
fCapOffsetY: Integer;
fOnClose: TKMEvent;
procedure UpdateSizes;
procedure Close(Sender: TObject);
procedure HandleOtherControlMouseMove(Sender: TObject; X,Y: Integer; Shift: TShiftState);
procedure HandleOtherControlMouseDown(Sender: TObject; X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
procedure HandleOtherControlMouseUp(Sender: TObject; X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
function MarginMainLeftRight: Integer;
function MarginMainTop: Integer;
function MarginMainBottom: Integer;
function MarginCrossTop: Integer;
function MarginCrossRight: Integer;
function GetActualWidth: Integer;
procedure SetActualWidth(aValue: Integer);
function GetActualHeight: Integer;
procedure SetActualHeight(aValue: Integer);
procedure SetHandleCloseKey(aValue: Boolean);
procedure SetCapOffsetY(aValue: Integer);
function GetCaption: string;
procedure SetCaption(const aValue: string);
protected
Bevel_Contents: TKMBevel;
Bevel_ModalBackground: TKMBevel;
Image_Background, Image_Close: TKMImage;
Label_Caption: TKMLabel;
procedure SetWidth(aValue: Integer); override;
procedure SetHeight(aValue: Integer); override;
public
ItemsPanel: TKMPanel;
DragEnabled: Boolean;
constructor Create(aParent: TKMPanel; aContentWidth, aContentHeight: Integer; const aCaption: UnicodeString = '';
aBackground: TKMFormBackgroundType = fbYellow; aCloseIcon: Boolean = False;
aBevelForContents: Boolean = True; aModalBackground: Boolean = True);
procedure MouseDown (X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
procedure MouseMove (X,Y: Integer; Shift: TShiftState); override;
procedure MouseUp (X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
function KeyUp(Key: Word; Shift: TShiftState): Boolean; override;
property OnClose: TKMEvent read fOnClose write fOnClose;
property ActualHeight: Integer read GetActualHeight write SetActualHeight;
property ActualWidth: Integer read GetActualWidth write SetActualWidth;
property CapOffsetY: Integer read fCapOffsetY write SetCapOffsetY;
property Caption: string read GetCaption write SetCaption;
property HandleCloseKey: Boolean read fHandleCloseKey write SetHandleCloseKey;
end;
implementation
uses
Math,
KM_RenderUI,
KM_ResTexts, KM_ResKeys, KM_ResTypes;
{ TKMForm }
constructor TKMForm.Create(aParent: TKMPanel; aContentWidth, aContentHeight: Integer; const aCaption: UnicodeString = '';
aBackground: TKMFormBackgroundType = fbYellow; aCloseIcon: Boolean = False;
aBevelForContents: Boolean = True; aModalBackground: Boolean = True);
const
BG_RX: array [TKMFormBackgroundType] of TRXType = (rxGuiMain, rxGuiMain, rxGui);
BG_ID: array [TKMFormBackgroundType] of Word = (15, 18, 409);
begin
fBackground := aBackground;
var desiredWidth := aContentWidth + 2 * MarginMainLeftRight;
var desiredHeight := aContentHeight + MarginMainBottom + MarginMainTop;
var desiredLeft := Max(0, (aParent.Width - desiredWidth) div 2);
var desiredTop := Max(0, (aParent.Height - desiredHeight) div 2);
// Create panel with calculated sizes
inherited Create(aParent, desiredLeft, desiredTop, desiredWidth, desiredHeight);
// Fix its base sizes as a desired one
BaseWidth := desiredWidth;
BaseHeight := desiredHeight;
// FitInParent := True; not sure if this is ever needed
DragEnabled := False;
fHandleCloseKey := False;
fCapOffsetY := 0;
if aModalBackground then
Bevel_ModalBackground := TKMBevel.Create(Self, -5000, -5000, 10000, 10000);
Image_Background := TKMImage.Create(Self, 0, 0, desiredWidth, desiredHeight, BG_ID[fBackground], BG_RX[fBackground]);
Image_Background.AnchorsStretch;
Image_Background.ImageStretch;
if aCloseIcon then
begin
Image_Close := TKMImage.Create(Self, Width - MarginCrossRight, MarginCrossTop, 31, 30, 52);
Image_Close.Anchors := [anTop, anRight];
Image_Close.Hint := gResTexts[TX_MSG_CLOSE_HINT];
Image_Close.OnClick := Close;
Image_Close.HighlightOnMouseOver := True;
end;
ItemsPanel := TKMPanel.Create(Self, MarginMainLeftRight, MarginMainTop, Width - 2*MarginMainLeftRight, Height - MarginMainTop - MarginMainBottom);
ItemsPanel.AnchorsStretch;
if aBevelForContents then
begin
Bevel_Contents := TKMBevel.Create(ItemsPanel, 0, 0, ItemsPanel.Width, ItemsPanel.Height);
Bevel_Contents.AnchorsStretch;
end;
Label_Caption := TKMLabel.Create(ItemsPanel, 0, -25, ItemsPanel.Width, 20, aCaption, DEFAULT_CAPTION_FONT, taCenter);
AnchorsCenter;
Hide;
fMasterControl.SubscribeOnOtherMouseMove(HandleOtherControlMouseMove);
fMasterControl.SubscribeOnOtherMouseDown(HandleOtherControlMouseDown);
fMasterControl.SubscribeOnOtherMouseUp(HandleOtherControlMouseUp);
end;
function TKMForm.MarginMainLeftRight: Integer;
const
MARGIN_SIDE: array [TKMFormBackgroundType] of Byte = (20, 35, 20);
begin
Result := MARGIN_SIDE[fBackground];
end;
function TKMForm.MarginMainTop: Integer;
const
MARGIN_TOP: array [TKMFormBackgroundType] of Byte = (40, 80, 50);
begin
Result := MARGIN_TOP[fBackground];
end;
function TKMForm.MarginMainBottom: Integer;
const
MARGIN_BOTTOM: array [TKMFormBackgroundType] of Byte = (20, 50, 20);
begin
Result := MARGIN_BOTTOM[fBackground];
end;
function TKMForm.MarginCrossTop: Integer;
const
CROSS_TOP: array [TKMFormBackgroundType] of Byte = (24, 40, 24);
begin
Result := CROSS_TOP[fBackground];
end;
function TKMForm.MarginCrossRight: Integer;
const
// Margin from right side, depends on bg graphics
CROSS_RIGHT: array [TKMFormBackgroundType] of Byte = (50, 130, 55);
begin
Result := CROSS_RIGHT[fBackground];
end;
function TKMForm.GetCaption: string;
begin
Result := Label_Caption.Caption;
end;
procedure TKMForm.Close(Sender: TObject);
begin
Hide;
if Assigned(fOnClose) then
fOnClose;
end;
procedure TKMForm.HandleOtherControlMouseDown(Sender: TObject; X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
if Sender = Image_Background then
MouseDown(X, Y, Shift, Button);
end;
procedure TKMForm.HandleOtherControlMouseMove(Sender: TObject; X, Y: Integer; Shift: TShiftState);
begin
inherited;
MouseMove(X, Y, Shift);
end;
procedure TKMForm.HandleOtherControlMouseUp(Sender: TObject; X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
MouseUp(X, Y, Shift, Button);
end;
procedure TKMForm.MouseDown(X, Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
inherited;
if not DragEnabled then Exit;
fDragging := True;
fDragStartPos := TKMPoint.New(X,Y);
end;
procedure TKMForm.MouseMove(X, Y: Integer; Shift: TShiftState);
begin
inherited;
if not DragEnabled or not fDragging then Exit;
Left := EnsureRange(Left + X - fDragStartPos.X, 0, fMasterControl.MasterPanel.Width - Width);
Top := EnsureRange(Top + Y - fDragStartPos.Y, -Image_Background.Top, fMasterControl.MasterPanel.Height - Height);
fDragStartPos := TKMPoint.New(X,Y);
end;
procedure TKMForm.MouseUp(X, Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
inherited;
if not DragEnabled then Exit;
fDragging := False;
end;
function TKMForm.KeyUp(Key: Word; Shift: TShiftState): Boolean;
begin
Result := inherited;
if Result then Exit; // Key already handled
if not fHandleCloseKey then Exit;
if Key = gResKeys[kfCloseMenu] then
begin
Close(nil);
Result := True;
end;
end;
procedure TKMForm.SetHeight(aValue: Integer);
begin
inherited;
UpdateSizes;
end;
procedure TKMForm.SetWidth(aValue: Integer);
begin
inherited;
UpdateSizes;
end;
procedure TKMForm.UpdateSizes;
begin
// Reposition buttons?
end;
function TKMForm.GetActualWidth: Integer;
begin
Result := ItemsPanel.Width;
end;
procedure TKMForm.SetActualWidth(aValue: Integer);
var
baseW: Integer;
begin
baseW := aValue + MarginMainLeftRight*2;
SetWidth(Min(Parent.Width, baseW));
end;
function TKMForm.GetActualHeight: Integer;
begin
Result := ItemsPanel.Height;
end;
procedure TKMForm.SetActualHeight(aValue: Integer);
var
baseH, h: Integer;
begin
baseH := aValue + MarginMainBottom + MarginMainTop;
h := Min(Parent.Height, baseH);
SetHeight(h);
end;
procedure TKMForm.SetHandleCloseKey(aValue: Boolean);
begin
fHandleCloseKey := aValue;
Focusable := aValue;
end;
procedure TKMForm.SetCapOffsetY(aValue: Integer);
begin
Label_Caption.Top := Label_Caption.Top + aValue - fCapOffsetY;
fCapOffsetY := aValue;
end;
procedure TKMForm.SetCaption(const aValue: string);
begin
Label_Caption.Caption := aValue;
end;
end.
File diff suppressed because it is too large. Load diff
+783
View File
@@ -0,0 +1,783 @@
unit KM_ControlsMemo;
{$I KaM_Remake.inc}
interface
uses
Classes, Controls,
KM_Controls, KM_ControlsScroll,
KM_RenderUI, KM_ResFonts,
KM_CommonTypes, KM_Points;
type
TKMMemo = class(TKMControl)
private
fFont: TKMFont; //Should not be changed from inital value, it will mess up the word wrapping
fItemHeight: Byte;
fItems: TStringList;
fWordWrap: Boolean;
fIndentAfterNL: Boolean;
fText: UnicodeString;
fScrollDown: Boolean;
fScrollBar: TKMScrollBar;
fOnChange: TNotifyEvent;
fCursorPos: Integer;
fSelectable: Boolean;
fSelectionStart: Integer;
fSelectionEnd: Integer;
fSelectionInitialPos: Integer;
procedure SetWordWrap(const Value: Boolean);
function GetText: UnicodeString;
procedure SetText(const aText: UnicodeString);
function GetTopIndex: smallint;
procedure SetTopIndex(aIndex: SmallInt);
procedure ReformatText;
procedure UpdateScrollBar;
function KeyEventHandled(Key: Word; Shift: TShiftState): Boolean;
procedure SetCursorPos(aPos: Integer);
function LinearToPointPos(aPos: Integer): TKMPoint;
function PointToLinearPos(aColumn, aRow: Integer): Integer;
function GetCharPosAt(X,Y: Integer): TKMPoint;
function GetCursorPosAt(X,Y: Integer): Integer;
procedure ResetSelection;
function HasSelection: Boolean;
function GetPlainText(aUsePipeAsEOL: Boolean; aStart: Integer = -1; aEnd: Integer = -1): UnicodeString;
function GetSelectedText: UnicodeString;
function GetMaxCursorPos: Integer;
function GetMaxPosInRow(aRow: Integer): Integer;
function GetMinPosInRow(aRow: Integer): Integer;
function GetMaxCursorPosInRow: Integer;
function GetMinCursorPosInRow: Integer;
procedure SetSelectionStart(aValue: Integer);
procedure SetSelectionEnd(aValue: Integer);
procedure SetSelections(aValue1, aValue2: Integer);
procedure UpdateSelection(aOldCursorPos: Integer; aIsSelecting: Boolean);
protected
procedure SetHeight(aValue: Integer); override;
procedure SetWidth(aValue: Integer); override;
procedure SetVisible(aValue: Boolean); override;
procedure SetEnabled(aValue: Boolean); override;
procedure SetTop(aValue: Integer); override;
procedure FocusChanged(aFocused: Boolean); override;
procedure ControlMouseDown(Sender: TObject; X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
function DoHandleMouseWheelByDefault: Boolean; override;
public
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont; aStyle: TKMButtonStyle; aSelectable: Boolean = True);
destructor Destroy; override;
procedure Add(const aItem: UnicodeString);
procedure Clear;
procedure ScrollToBottom;
property WordWrap: Boolean read fWordWrap write SetWordWrap; //Whether to automatically wrap text within given text area width
property IndentAfterNL: Boolean read fIndentAfterNL write fIndentAfterNL;
property Text: UnicodeString read GetText write SetText;
property ItemHeight: Byte read fItemHeight write fItemHeight;
property TopIndex: Smallint read GetTopIndex write SetTopIndex;
property ScrollDown: Boolean read fScrollDown write fScrollDown;
property CursorPos: Integer read fCursorPos write SetCursorPos;
property Selectable: Boolean read fSelectable write fSelectable;
property SelectionStart: Integer read fSelectionStart write SetSelectionStart;
property SelectionEnd: Integer read fSelectionEnd write SetSelectionEnd;
function GetVisibleRows: Integer;
function KeyDown(Key: Word; Shift: TShiftState): Boolean; override;
function KeyUp(Key: Word; Shift: TShiftState): Boolean; override;
procedure MouseWheel(Sender: TObject; WheelSteps: Integer; var aHandled: Boolean); override;
procedure MouseDown(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
procedure MouseMove(X,Y: Integer; Shift: TShiftState); override;
procedure MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
property OnChange: TNotifyEvent read fOnChange write fOnChange;
procedure Paint; override;
end;
implementation
uses
KM_Defaults,
{$IFDEF MSWindows} Windows, {$ENDIF}
{$IFDEF Unix} LCLIntf, LCLType, {$ENDIF}
SysUtils, StrUtils, Math,
Clipbrd,
KromUtils,
KM_ControlsUtils,
KM_Resource, KM_Render, KM_RenderTypes,
KM_CommonUtils;
{ TKMMemo }
constructor TKMMemo.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont; aStyle: TKMButtonStyle; aSelectable: Boolean = True);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
fItemHeight := 20;
fItems := TStringList.Create;
fFont := aFont;
fScrollBar := TKMScrollBar.Create(aParent, aLeft+aWidth-20, aTop, 20, aHeight, saVertical, aStyle);
UpdateScrollBar; //Initialise the scrollbar
fSelectable := aSelectable;
aParent.MasterControl.SubscribeOnOtherMouseDown(ControlMouseDown);
end;
destructor TKMMemo.Destroy;
begin
fItems.Free;
inherited;
end;
procedure TKMMemo.SetHeight(aValue: Integer);
begin
inherited;
fScrollBar.Height := Height;
UpdateScrollBar; //Since height has changed
end;
procedure TKMMemo.SetWidth(aValue: Integer);
begin
inherited;
fScrollBar.Left := Left + Width - fScrollBar.Width;
ReformatText; //Repositions the scroll bar as well
end;
//Copy property to scrollbar. Otherwise it won't be rendered
procedure TKMMemo.SetVisible(aValue: Boolean);
begin
inherited;
//Hide scrollbar and its buttons
fScrollBar.Visible := IsSetVisible and (fScrollBar.MaxValue <> fScrollBar.MinValue);
end;
function TKMMemo.GetTopIndex: smallint;
begin
Result := fScrollBar.Position;
end;
procedure TKMMemo.SetTopIndex(aIndex: smallint);
begin
fScrollBar.Position := aIndex;
end;
procedure TKMMemo.SetEnabled(aValue: Boolean);
begin
inherited;
fScrollBar.Enabled := Enabled;
end;
procedure TKMMemo.SetTop(aValue: Integer);
begin
inherited;
fScrollBar.Top := Top;
end;
procedure TKMMemo.SetWordWrap(const Value: boolean);
begin
fWordWrap := Value;
ReformatText;
end;
function TKMMemo.GetText: UnicodeString;
begin
Result := fText;
end;
procedure TKMMemo.SetText(const aText: UnicodeString);
begin
fText := aText;
ReformatText;
if Assigned(fOnChange) then
fOnChange(Self);
end;
procedure TKMMemo.ReformatText;
var
newText: UnicodeString;
begin
if fWordWrap then
newText := gRes.Fonts[fFont].WordWrap(fText, Width - fScrollBar.Width - 8, True, IndentAfterNL)
else
newText := fText;
//KaM uses | for new line, fItems.Text:= uses standard eol to parse each item from the string
fItems.Text := StringReplace(newText, '|', EolW, [rfReplaceAll]);
UpdateScrollBar;
end;
function TKMMemo.GetVisibleRows: Integer;
begin
Result := Height div fItemHeight;
end;
//fItems.Count or Height has changed
procedure TKMMemo.UpdateScrollBar;
var
oldMax: Integer;
begin
oldMax := fScrollBar.MaxValue;
fScrollBar.MaxValue := fItems.Count - GetVisibleRows;
fScrollBar.Visible := IsSetVisible and (fScrollBar.MaxValue <> fScrollBar.MinValue);
if fScrollDown then
begin
if oldMax - fScrollBar.Position <= 2 then //If they were near the bottom BEFORE updating, keep them at the bottom
SetTopIndex(fItems.Count) //This puts it at the bottom because of the EnsureRange in SetTopIndex
end
else
SetTopIndex(fScrollBar.Position);
end;
procedure TKMMemo.SetCursorPos(aPos: Integer);
begin
fCursorPos := EnsureRange(aPos, 0, GetMaxCursorPos);
end;
function TKMMemo.GetPlainText(aUsePipeAsEOL: Boolean; aStart: Integer = -1; aEnd: Integer = -1): UnicodeString;
begin
if aStart = -1 then
aStart := 0;
if aEnd = -1 then
aEnd := Length(fItems.Text);
// First remove EOL's to get correct positions in text
Result := StringReplace(fItems.Text, EolW, '|', [rfReplaceAll]);
// Get text with selected positions, cleaned of color markup
Result := Copy(GetNoColorMarkupText(Result), aStart + 1, aEnd - aStart);
// Return EOL's back
if not aUsePipeAsEOL then
Result := StringReplace(Result, '|', EolW, [rfReplaceAll]);
end;
function TKMMemo.GetSelectedText: UnicodeString;
begin
Result := '';
if HasSelection then
Result := GetPlainText(False, fSelectionStart, fSelectionEnd);
end;
function TKMMemo.HasSelection: Boolean;
begin
Result := (fSelectionStart <> -1) and (fSelectionEnd <> -1) and (fSelectionStart <> fSelectionEnd);
end;
procedure TKMMemo.ResetSelection;
begin
fSelectionStart := -1;
fSelectionEnd := -1;
fSelectionInitialPos := -1;
end;
//We are using 2 systems for position: Linear (cumulative) and 'Point' (2D with column and row)
//Every system have Length(RowText)+1 positions in every line
//Convert Linear position into 2D position
function TKMMemo.LinearToPointPos(aPos: Integer): TKMPoint;
var I, row, column: Integer;
rowText: UnicodeString;
rowStartPos, rowEndPos: Integer;
begin
row := 0;
column := 0;
for I := 0 to fItems.Count - 1 do
begin
rowText := GetNoColorMarkupText(fItems[I]);
rowStartPos := PointToLinearPos(0, I);
rowEndPos := rowStartPos + Length(rowText);
if InRange(aPos, rowStartPos, rowEndPos) then
begin
row := I;
column := aPos - rowStartPos;
Break;
end;
end;
Result := TKMPoint.New(column, row);
end;
//We are using 2 systems for position: Linear (cumulative) and 'Point' (2D with column and row)
//Every system have Length(RowText)+1 positions in every line
//Convert 2D position into Linear position
function TKMMemo.PointToLinearPos(aColumn, aRow: Integer): Integer;
var
I: Integer;
begin
Result := 0;
aRow := EnsureRange(aRow, 0, fItems.Count-1);
for I := 0 to aRow-1 do
Inc(Result, Length(GetNoColorMarkupText(fItems[I])) + 1); //+1 for special KaM new line symbol ('|')
Inc(Result, EnsureRange(aColumn, 0, GetMaxPosInRow(aRow)));
end;
//Return position of Cursor in 2D of point X,Y on the screen
function TKMMemo.GetCharPosAt(X,Y: Integer): TKMPoint;
var
row, column: Integer;
begin
row := 0;
column := 0;
if fItems.Count > 0 then
begin
row := (EnsureRange(Y-AbsTop-3, 0, Height-6) div fItemHeight) + TopIndex;
row := EnsureRange(row, 0, fItems.Count-1);
column := gRes.Fonts[fFont].CharsThatFit(GetNoColorMarkupText(fItems[row]), X-AbsLeft-4);
end;
Result := TKMPoint.New(column, row);
end;
//Return position of Cursor in linear system of point X,Y on the screen
function TKMMemo.GetCursorPosAt(X,Y: Integer): Integer;
var
charPos: TKMPoint;
begin
charPos := GetCharPosAt(X, Y);
Result := PointToLinearPos(charPos.X, charPos.Y);
end;
// Maximum possible position of Cursor
function TKMMemo.GetMaxCursorPos: Integer;
begin
Result := Length(StringReplace(fItems.Text, EolW, '', [rfReplaceAll])) + EnsureRange(fItems.Count-1, 0, fItems.Count);
end;
// Minimum possible position of Cursor in its current Row
function TKMMemo.GetMinCursorPosInRow: Integer;
begin
Result := GetMinPosInRow(LinearToPointPos(CursorPos).Y)
end;
// Maximum possible position of Cursor in its current Row
function TKMMemo.GetMaxCursorPosInRow: Integer;
begin
Result := GetMaxPosInRow(LinearToPointPos(CursorPos).Y)
end;
// Minimum possible position of Cursor in the specified aRow
function TKMMemo.GetMinPosInRow(aRow: Integer): Integer;
begin
if aRow = 0 then
Result := 0
else
Result := GetMaxPosInRow(aRow - 1) + 1;
end;
// Maximum possible position of Cursor in the specified aRow
function TKMMemo.GetMaxPosInRow(aRow: Integer): Integer;
var
I: Integer;
begin
Result := 0;
for I := 0 to min(aRow, fItems.Count - 1) do
Inc(Result, Length(GetNoColorMarkupText(fItems[I])) + 1);
Result := EnsureRange(Result - 1, 0, Result);
end;
procedure TKMMemo.SetSelectionStart(aValue: Integer);
var
maxPos: Integer;
begin
if fSelectable then
begin
maxPos := GetMaxCursorPos;
fSelectionStart := EnsureRange(aValue, 0, maxPos);
end;
end;
procedure TKMMemo.SetSelectionEnd(aValue: Integer);
var
maxPos: Integer;
begin
if fSelectable then
begin
maxPos := GetMaxCursorPos;
fSelectionEnd := EnsureRange(aValue, 0, maxPos);
end;
end;
//Set selections with pair of value, using he fact, that fSelectionStart <= fSelectionEnd
procedure TKMMemo.SetSelections(aValue1, aValue2: Integer);
begin
fSelectionStart := Min(aValue1, aValue2);
fSelectionEnd := Max(aValue1, aValue2);
end;
//Update selection start/end due to change cursor position
procedure TKMMemo.UpdateSelection(aOldCursorPos: Integer; aIsSelecting: Boolean);
begin
if aIsSelecting then
begin
if HasSelection then
begin
if aOldCursorPos = SelectionStart then
SetSelections(CursorPos, SelectionEnd)
else
if aOldCursorPos = SelectionEnd then
SetSelections(SelectionStart, CursorPos);
end else
SetSelections(aOldCursorPos, CursorPos);
end
else
ResetSelection;
end;
procedure TKMMemo.Add(const aItem: UnicodeString);
begin
if fText <> '' then
fText := fText + '|';
fText := fText + aItem; //Append the new string
SetText(fText); //Updates the text in fItems
UpdateScrollBar; //Scroll down with each item that is added.
if Assigned(fOnChange) then
fOnChange(Self);
end;
procedure TKMMemo.Clear;
begin
fText := '';
fItems.Clear;
ResetSelection;
UpdateScrollBar;
if Assigned(fOnChange) then
fOnChange(Self);
end;
procedure TKMMemo.ScrollToBottom;
begin
SetTopIndex(fItems.Count);
end;
//Key events which have no effect should not be handled (allows scrolling while chat window open with no text entered)
function TKMMemo.KeyEventHandled(Key: Word; Shift: TShiftState): Boolean;
begin
Result := True;
//Don't include backspace/delete because edits should always handle those. Otherwise when you
//press backspace repeatedly to remove all characters it will apply other shortcuts like
//resetting the zoom if you press it once too many times.
case Key of
VK_UP,
VK_DOWN,
VK_LEFT,
VK_RIGHT,
VK_HOME,
VK_END,
VK_PRIOR,
VK_NEXT: Result := (fText <> ''); //These keys have no effect when text is blank
end;
//We want these keys to be ignored by chat, so game shortcuts still work
if Key in [VK_F1..VK_F12, VK_ESCAPE, VK_MBUTTON] then Result := False;
//Ctrl can be used as an escape character, e.g. CTRL+B places beacon while chat is open
if ssCtrl in Shift then Result := (Key in [VK_LEFT, VK_RIGHT, Ord('A'), Ord('C'), Ord('X')]);
end;
function TKMMemo.KeyDown(Key: Word; Shift: TShiftState): Boolean;
//Move cursor vertically (change cursor row)
procedure MoveCursorVertically(aRowIncrement: Integer);
var
cursorPointPos: TKMPoint;
newCursorPosY: Integer;
srcLineText, destLineText: UnicodeString;
begin
cursorPointPos := LinearToPointPos(CursorPos);
newCursorPosY := EnsureRange(cursorPointPos.Y + aRowIncrement, 0, fItems.Count-1);
if newCursorPosY <> cursorPointPos.Y then
begin
// Because we don't use monospaces fonts, then its better to find proper column, which depends of text width in px
srcLineText := GetNoColorMarkupText(Copy(fItems[cursorPointPos.Y], 1, cursorPointPos.X));
destLineText := GetNoColorMarkupText(fItems[newCursorPosY]);
//Use 'rounding' version of CharsThatFit to get more precise position
cursorPointPos.X := gRes.Fonts[fFont].CharsThatFit(destLineText, gRes.Fonts[fFont].GetTextSize(srcLineText).X, True);
CursorPos := PointToLinearPos(cursorPointPos.X, newCursorPosY);
// Update scroll position, if needed
if TopIndex > newCursorPosY then
TopIndex := newCursorPosY
else if TopIndex < newCursorPosY - GetVisibleRows + 1 then
TopIndex := newCursorPosY - GetVisibleRows + 1;
end;
end;
// Update cursor pos considering Ctrl Key to go to the next / prev word in the text
procedure UpdateCursorPos(aCursorDir: TKMCursorDir);
begin
if ssCtrl in Shift then
begin
case aCursorDir of
cdNone: ;
cdForward: CursorPos := GetNextWordPos(GetPlainText(True), CursorPos);
cdBack: CursorPos := GetPrevWordPos(GetPlainText(True), CursorPos);
end;
end
else
CursorPos := CursorPos + ShortInt(aCursorDir);
end;
var
oldCursorPos: Integer;
isSelect: Boolean;
begin
Result := KeyEventHandled(Key, Shift);
if inherited KeyDown(Key, Shift) then Exit;
//Clipboard operations
if (Shift = [ssCtrl]) and (Key <> VK_CONTROL) then
case Key of
Ord('A'): SetSelections(0, GetMaxCursorPos);
Ord('C'),
Ord('X'): if HasSelection then
Clipboard.AsText := GetSelectedText;
end;
oldCursorPos := CursorPos;
isSelect := IsSelecting(Key, Shift);
case Key of
VK_LEFT,
VK_RIGHT: begin
UpdateCursorPos(GetCursorDir(Key));
UpdateSelection(oldCursorPos, isSelect);
end;
VK_UP: begin
MoveCursorVertically(-1);
UpdateSelection(oldCursorPos, isSelect);
end;
VK_DOWN: begin
MoveCursorVertically(1);
UpdateSelection(oldCursorPos, isSelect);
end;
VK_PRIOR: begin
MoveCursorVertically(-GetVisibleRows);
UpdateSelection(oldCursorPos, isSelect);
end;
VK_NEXT: begin
MoveCursorVertically(GetVisibleRows);
UpdateSelection(oldCursorPos, isSelect);
end;
VK_HOME: begin
CursorPos := GetMinCursorPosInRow;
UpdateSelection(oldCursorPos, isSelect);
end;
VK_END: begin
CursorPos := GetMaxCursorPosInRow;
UpdateSelection(oldCursorPos, isSelect);
end;
end
end;
function TKMMemo.KeyUp(Key: Word; Shift: TShiftState): Boolean;
begin
Result := inherited KeyUp(Key, Shift) or KeyEventHandled(Key, Shift);
end;
function TKMMemo.DoHandleMouseWheelByDefault: Boolean;
begin
Result := False;
end;
procedure TKMMemo.MouseWheel(Sender: TObject; WheelSteps: Integer; var aHandled: Boolean);
begin
inherited;
if aHandled then Exit;
SetTopIndex(TopIndex - WheelSteps);
aHandled := WheelSteps <> 0;
end;
procedure TKMMemo.MouseDown(X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
var
oldCursorPos: Integer;
begin
inherited;
Focusable := fSelectable and (fText <> ''); // Do not focus on empty Memo's
// Update Focus now, because we need to focus on MouseDown, not on MouseUp as by default for all controls
MasterParent.MasterControl.UpdateFocus(Self);
oldCursorPos := CursorPos;
CursorPos := GetCursorPosAt(X, Y);
if Focusable then
begin
//Try select on Shift + LMB click
UpdateSelection(oldCursorPos, (oldCursorPos <> -1) and (Shift = [ssLeft, ssShift]));
fSelectionInitialPos := CursorPos;
end;
end;
procedure TKMMemo.MouseMove(X,Y: Integer; Shift: TShiftState);
var
oldCursorPos: Integer;
charPos: TKMPoint;
begin
inherited;
if (ssLeft in Shift) and (fSelectionInitialPos <> -1) then
begin
charPos := GetCharPosAt(X, Y);
// To rotate text to top while selecting
if (Y-AbsTop-3 < 0) and (TopIndex > 0) then
begin
Dec(charPos.Y);
SetTopIndex(TopIndex-1);
end;
// To rotate text to bottom while selecting
if (Y-AbsTop-3 > Height)
and (fScrollBar.Position < fScrollBar.MaxValue) then
begin
charPos.Y := EnsureRange(charPos.Y+1, 0, fItems.Count);
SetTopIndex(TopIndex+1);
end;
oldCursorPos := CursorPos;
CursorPos := PointToLinearPos(charPos.X, charPos.Y);;
UpdateSelection(oldCursorPos, True);
end;
end;
procedure TKMMemo.MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
inherited;
fSelectionInitialPos := -1;
end;
procedure TKMMemo.ControlMouseDown(Sender: TObject; X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
// Reset all, if other control was clicked
if Sender <> Self then
begin
ResetSelection;
Focusable := False;
CursorPos := -1;
MasterParent.MasterControl.UpdateFocus(Self);
end;
end;
procedure TKMMemo.FocusChanged(aFocused: Boolean);
begin
if not aFocused then
begin
ResetSelection;
CursorPos := -1;
Focusable := False;
end;
end;
procedure TKMMemo.Paint;
var
I, paintWidth, selPaintTop, selPaintHeight: Integer;
beforeSelectionText, selectionText, rowText: UnicodeString;
beforeSelectionW, selectionW, selStartPosInRow, selEndPosInRow, rowStartPos, rowEndPos: Integer;
offX, offY: Integer;
begin
inherited;
if fScrollBar.Visible then
paintWidth := Width - fScrollBar.Width //Leave space for scrollbar
else
paintWidth := Width; //List takes up the entire width
TKMRenderUI.WriteBevel(AbsLeft, AbsTop, paintWidth, Height, 1, 0.5);
for I := 0 to Math.min(fItems.Count-1, GetVisibleRows - 1) do
begin
rowText := GetNoColorMarkupText(fItems[TopIndex+I]);
rowStartPos := PointToLinearPos(0, TopIndex+I);
rowEndPos := rowStartPos + Length(rowText);
if HasSelection then
begin
selStartPosInRow := fSelectionStart - rowStartPos;
selEndPosInRow := fSelectionEnd - rowStartPos;
selStartPosInRow := EnsureRange(selStartPosInRow, 0, rowEndPos);
selEndPosInRow := EnsureRange(selEndPosInRow, selStartPosInRow, rowEndPos);
if selStartPosInRow <> selEndPosInRow then
begin
beforeSelectionText := Copy(rowText, 1, selStartPosInRow);
selectionText := Copy(rowText, selStartPosInRow+1, selEndPosInRow - selStartPosInRow);
beforeSelectionW := gRes.Fonts[fFont].GetTextSize(beforeSelectionText).X;
selectionW := gRes.Fonts[fFont].GetTextSize(selectionText).X;
selPaintHeight := fItemHeight;
selPaintTop := AbsTop+I*fItemHeight;
if I = 0 then
begin
Dec(selPaintHeight, 3);
Inc(selPaintTop, 3);
end;
TKMRenderUI.WriteShape(AbsLeft+4+beforeSelectionW, selPaintTop, min(selectionW, Width-8), selPaintHeight, clTextSelection);
end;
end;
//Render text cursor
if fSelectable and (csFocus in State) and ((TimeGet div 500) mod 2 = 0)
and InRange(CursorPos, rowStartPos, rowEndPos) then
begin
offX := AbsLeft + 2 + gRes.Fonts[fFont].GetTextSize(Copy(rowText, 1, CursorPos-rowStartPos)).X;
offY := AbsTop + 2 + I*fItemHeight;
TKMRenderUI.WriteShape(offX, offY, 3, fItemHeight-4, $FFFFFFFF, $FF000000);
end;
TKMRenderUI.WriteText(AbsLeft+4, AbsTop+I*fItemHeight+3, Width-8, fItems.Strings[TopIndex+I] , fFont, taLeft);
end;
end;
end.
@@ -0,0 +1,339 @@
unit KM_ControlsMinimapView;
{$I KaM_Remake.inc}
interface
uses
Classes, Controls,
KromOGLUtils,
KM_RenderUI, KM_Minimap, KM_Viewport,
KM_ResFonts, KM_CommonTypes, KM_Points, KM_Defaults,
KM_Controls, KM_ControlsBase;
type
TKMMinimapEvent = procedure (Sender: TObject; const aLocX, aLocY: Integer) of object;
TKMMinimapLocEvent = procedure (Sender: TObject; const aLoc: Integer) of object;
// MinimapView relies on fMinimap and fViewport that provide all the data
// MinimapView itself is just a painter
TKMMinimapView = class(TKMControl)
private
fBevel: TKMBevel;
fMinimap: TKMMinimap;
fView: TKMViewport;
fPaintWidth: Integer;
fPaintHeight: Integer;
fLeftOffset: Integer;
fTopOffset: Integer;
fMapTex: TTexture;
fWidthPOT: Word;
fHeightPOT: Word;
fOnMinimapChange: TKMMinimapEvent;
fOnMinimapClick: TKMMinimapEvent;
fShowLocs: Boolean;
fLocRad: Byte;
fClickableOnce: Boolean;
procedure UpdateTexture;
procedure ResizeMinimap;
protected
procedure SetAnchors(aValue: TKMAnchorsSet); override;
public
OnMinimapLocationClick: TKMMinimapLocEvent;
constructor Create(aMinimap: TKMMinimap; aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aWithBevel: Boolean = False);
function LocalToMapCoords(X,Y: Integer): TKMPoint;
function MapCoordsToLocal(X,Y: Single; const Inset: ShortInt = 0): TKMPoint;
procedure UpdateSizes;
procedure SetViewport(aViewport: TKMViewport);
property ShowLocs: Boolean read fShowLocs write fShowLocs;
property ClickableOnce: Boolean read fClickableOnce write fClickableOnce;
property OnMinimapChange: TKMMinimapEvent write fOnMinimapChange;
property OnMinimapClick: TKMMinimapEvent read fOnMinimapClick write fOnMinimapClick;
procedure MouseDown(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
procedure MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
procedure MouseMove(X,Y: Integer; Shift: TShiftState); override;
procedure Paint; override;
end;
implementation
uses
SysUtils,
Math,
KromUtils,
KM_MinimapGame,
KM_Render, KM_RenderTypes,
KM_CommonUtils,
KM_GameSettings, KM_InterfaceTypes;
{ TKMMinimapView }
constructor TKMMinimapView.Create(aMinimap: TKMMinimap; aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aWithBevel: Boolean = False);
begin
//Create Bevel first
if aWithBevel then
fBevel := TKMBevel.Create(aParent, aLeft - 4, aTop - 4, aWidth + 8, aHeight + 8);
//Then Minimap control itself
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
//Radius of circle around player location
fLocRad := 8;
fMinimap := aMinimap;
if fMinimap <> nil then
begin
fMinimap.SubOnUpdateTexture(UpdateTexture);
fMinimap.SubOnResize(ResizeMinimap);
fMapTex.Tex := TKMRender.GenerateTextureCommon(ftNearest, ftNearest);
end;
end;
procedure TKMMinimapView.UpdateSizes;
begin
if fMinimap.MapX > fMinimap.MapY then
begin
fPaintWidth := Width;
fPaintHeight := Round(Height * fMinimap.MapY / Max(fMinimap.MapX, 1)); // X could = 0
fLeftOffset := 0;
fTopOffset := (Height - fPaintHeight) div 2;
end
else
begin
fPaintWidth := Round(Width * fMinimap.MapX / Max(fMinimap.MapY, 1)); // Y could = 0
fPaintHeight := Height;
fLeftOffset := (Width - fPaintWidth) div 2;
fTopOffset := 0;
end;
end;
procedure TKMMinimapView.SetViewport(aViewport: TKMViewport);
begin
fView := aViewport;
end;
procedure TKMMinimapView.ResizeMinimap;
begin
if Self = nil then Exit;
fWidthPOT := MakePOT(fMinimap.MapX);
fHeightPOT := MakePOT(fMinimap.MapY);
fMapTex.U := fMinimap.MapX / fWidthPOT;
fMapTex.V := fMinimap.MapY / fHeightPOT;
end;
procedure TKMMinimapView.UpdateTexture;
var
wData: Pointer;
I: Word;
begin
if Self = nil then Exit;
GetMem(wData, fWidthPOT * fHeightPOT * 4);
if fMinimap.MapY > 0 then //if MapY = 0 then loop will overflow to MaxWord
for I := 0 to fMinimap.MapY - 1 do
Move(Pointer(NativeUint(fMinimap.Base) + I * fMinimap.MapX * 4)^,
Pointer(NativeUint(wData) + I * fWidthPOT * 4)^, fMinimap.MapX * 4);
TKMRender.UpdateTexture(fMapTex.Tex, fWidthPOT, fHeightPOT, tfRGBA8, wData);
FreeMem(wData);
UpdateSizes;
end;
function TKMMinimapView.LocalToMapCoords(X,Y: Integer): TKMPoint;
begin
Result.X := EnsureRange(Trunc((X - AbsLeft - fLeftOffset) * (fMinimap.MapX + 1) / fPaintWidth), 1, fMinimap.MapX);
Result.Y := EnsureRange(Trunc((Y - AbsTop - fTopOffset ) * (fMinimap.MapY + 1) / fPaintHeight), 1, fMinimap.MapY);
end;
function TKMMinimapView.MapCoordsToLocal(X,Y: Single; const Inset: ShortInt = 0): TKMPoint;
begin
Assert(Inset >= -1, 'Min allowed inset is -1, to be within TKMPoint range of 0..n');
Result.X := AbsLeft + fLeftOffset + EnsureRange(Round(X * fPaintWidth / fMinimap.MapX), Inset, fPaintWidth - Inset);
Result.Y := AbsTop + fTopOffset + EnsureRange(Round(Y * fPaintHeight / fMinimap.MapY), Inset, fPaintHeight - Inset);
end;
procedure TKMMinimapView.SetAnchors(aValue: TKMAnchorsSet);
begin
inherited;
if fBevel <> nil then
fBevel.Anchors := aValue;
end;
procedure TKMMinimapView.MouseDown(X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
inherited;
MouseMove(X,Y,Shift);
end;
procedure TKMMinimapView.MouseMove(X,Y: Integer; Shift: TShiftState);
var
viewPos: TKMPoint;
begin
inherited;
if (ssLeft in Shift) and not fClickableOnce then
begin
viewPos := LocalToMapCoords(X,Y);
if Assigned(fOnMinimapChange) then
fOnMinimapChange(Self, viewPos.X, viewPos.Y);
end;
end;
procedure TKMMinimapView.MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
var
I: Integer;
T, viewPos: TKMPoint;
begin
inherited;
if fClickableOnce then
begin
fClickableOnce := False; //Not clickable anymore
viewPos := LocalToMapCoords(X,Y);
if Assigned(fOnMinimapClick) then
fOnMinimapClick(Self, viewPos.X, viewPos.Y);
end;
if fShowLocs then
for I := 0 to MAX_HANDS - 1 do
if fMinimap.HandShow[I] and not KMSamePoint(fMinimap.HandLocs[I], KMPOINT_ZERO) then
begin
T := MapCoordsToLocal(fMinimap.HandLocs[I].X, fMinimap.HandLocs[I].Y, fLocRad);
if Sqr(T.X - X) + Sqr(T.Y - Y) < Sqr(fLocRad) then
begin
if Assigned(OnMinimapLocationClick) then
OnMinimapLocationClick(Self, I);
// Do not repeat events for overlapping locations
Break;
end;
end;
end;
procedure TKMMinimapView.Paint;
const
ALERT_RAD = 4;
var
I, K: Integer;
R: TKMRect;
S: TKMDirection4Set;
T, T1, T2: TKMPoint;
minimapGame: TKMMiniMapGame;
miniLeft, miniTop, miniRight, miniBottom: SmallInt;
begin
inherited;
if (fMinimap = nil) or (fMinimap.MapX * fMinimap.MapY = 0) then
Exit;
if (fMapTex.Tex <> 0) then
TKMRenderUI.WriteTexture(AbsLeft + fLeftOffset, AbsTop + fTopOffset, fPaintWidth, fPaintHeight, fMapTex, $FFFFFFFF)
else
TKMRenderUI.WriteBevel(AbsLeft, AbsTop, Width, Height);
//Alerts (under viewport rectangle)
if fMinimap is TKMMiniMapGame then
begin
minimapGame := TKMMiniMapGame(fMinimap);
if (minimapGame.Alerts <> nil) then
for I := 0 to minimapGame.Alerts.Count - 1 do
if minimapGame.Alerts[I].VisibleMinimap then
begin
T := MapCoordsToLocal(minimapGame.Alerts[I].Loc.X, minimapGame.Alerts[I].Loc.Y, ALERT_RAD);
TKMRenderUI.WritePicture(T.X, T.Y, 0, 0, [],
minimapGame.Alerts[I].TexMinimap.RX, minimapGame.Alerts[I].TexMinimap.ID,
True, minimapGame.Alerts[I].TeamColor, Abs((TimeGet mod 1000) / 500 - 1));
end;
//Viewport rectangle
if fView <> nil then
begin
R := fView.GetMinimapClip;
if (R.Right - R.Left) * (R.Bottom - R.Top) > 0 then
begin
if gGameSettings.ZoomBehaviour = zbRestricted then
begin
TKMRenderUI.WriteOutline(AbsLeft + fLeftOffset + Round((R.Left - 1)*fPaintWidth / fMinimap.MapX),
AbsTop + fTopOffset + Round((R.Top - 1)*fPaintHeight / fMinimap.MapY),
Round((R.Right - R.Left)*fPaintWidth / fMinimap.MapX),
Round((R.Bottom - R.Top)*fPaintHeight / fMinimap.MapY), 1, $FFFFFFFF);
end
else
begin
miniLeft := AbsLeft + fLeftOffset + Round((R.Left - 1)*fPaintWidth / fMinimap.MapX) + 1;
miniTop := AbsTop + fTopOffset + Round((R.Top - 1)*fPaintHeight / fMinimap.MapY) + 1;
miniRight := AbsLeft + fLeftOffset + Round((R.Right - 1)*fPaintWidth / fMinimap.MapX);
miniBottom := AbsTop + fTopOffset + Round((R.Bottom - 1)*fPaintHeight / fMinimap.MapY);
S := fView.GetMinimapClipLines;
if drW in S then
TKMRenderUI.WriteLine(miniLeft, miniBottom, miniLeft, miniTop, $FFFFFFFF);
if drN in S then
TKMRenderUI.WriteLine(miniLeft-1, miniTop, miniRight, miniTop, $FFFFFFFF);
if drE in S then
TKMRenderUI.WriteLine(miniRight, miniBottom, miniRight, miniTop, $FFFFFFFF);
if drS in S then
TKMRenderUI.WriteLine(miniLeft-1, miniBottom, miniRight, miniBottom, $FFFFFFFF);
end;
end;
end;
end;
if fShowLocs then
begin
//Connect allied players
for I := 0 to MAX_HANDS - 1 do
if fMinimap.HandShow[I] and not KMSamePoint(fMinimap.HandLocs[I], KMPOINT_ZERO) then
for K := I + 1 to MAX_HANDS - 1 do
if fMinimap.HandShow[K] and not KMSamePoint(fMinimap.HandLocs[K], KMPOINT_ZERO) then
if (fMinimap.HandTeam[I] <> 0) and (fMinimap.HandTeam[I] = fMinimap.HandTeam[K]) then
begin
T1 := MapCoordsToLocal(fMinimap.HandLocs[I].X, fMinimap.HandLocs[I].Y, fLocRad);
T2 := MapCoordsToLocal(fMinimap.HandLocs[K].X, fMinimap.HandLocs[K].Y, fLocRad);
TKMRenderUI.WriteLine(T1.X, T1.Y, T2.X, T2.Y, $FFFFFFFF);
end;
//Draw all the circles, THEN all the numbers so the numbers are not covered by circles when they are close
for I := 0 to MAX_HANDS - 1 do
if fMinimap.HandShow[I] and not KMSamePoint(fMinimap.HandLocs[I], KMPOINT_ZERO) then
begin
T := MapCoordsToLocal(fMinimap.HandLocs[I].X, fMinimap.HandLocs[I].Y, fLocRad);
TKMRenderUI.WriteCircle(T.X, T.Y, fLocRad, fMinimap.HandColors[I]);
end;
for I := 0 to MAX_HANDS - 1 do
if fMinimap.HandShow[I] and not KMSamePoint(fMinimap.HandLocs[I], KMPOINT_ZERO) then
begin
T := MapCoordsToLocal(fMinimap.HandLocs[I].X, fMinimap.HandLocs[I].Y, fLocRad);
TKMRenderUI.WriteText(T.X, T.Y - 6, 0, IntToStr(I+1), fntOutline, taCenter);
end;
end;
if not Enabled then
TKMRenderUI.WriteBevel(AbsLeft, AbsTop, Width+1, Height+1, 0, 0.5);
end;
end.
+176
View File
@@ -0,0 +1,176 @@
unit KM_ControlsPopUp;
{$I KaM_Remake.inc}
interface
uses
Classes, Controls,
KM_Controls, KM_ControlsBase, KM_ControlsList,
KM_CommonTypes, KM_Points,
KM_ResFonts;
type
TKMPopUpMenuMode = (
pmActOnMouseUp, // Action triggered only by mouse up (default)
pmActOnMouseDownNMove // Action triggered by mouse down and mouse move (drag)
);
type
// Popup with a short list (context menu)
// For bigger taks - use TKMForm!
TKMPopUpMenu = class(TKMPanel)
private
fMenuMode: TKMPopUpMenuMode;
procedure MenuHide(Sender: TObject);
procedure MenuActionTriggered(Sender: TObject);
procedure HandleOtherControlMouseUp(Sender: TObject; X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
procedure SetItemIndex(aValue: Integer);
function GetItemIndex: Integer;
function GetItemTag(aIndex: Integer): Integer;
protected
Shape_Background: TKMShape;
ColumnBox_List: TKMColumnBox;
public
constructor Create(aParent: TKMPanel; aWidth: Integer; aMenuMode: TKMPopUpMenuMode = pmActOnMouseUp);
procedure AddItem(const aCaption: UnicodeString; aTag: Integer = 0);
procedure UpdateItem(aIndex: Integer; const aCaption: UnicodeString);
procedure Clear;
property ItemIndex: Integer read GetItemIndex write SetItemIndex;
property ItemTags[aIndex: Integer]: Integer read GetItemTag;
procedure ShowAt(X,Y: Integer);
procedure HideMenu;
property MenuMode: TKMPopUpMenuMode read fMenuMode;
procedure MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
end;
implementation
uses
Math,
KM_RenderUI;
{ TKMPopUpMenu }
constructor TKMPopUpMenu.Create(aParent: TKMPanel; aWidth: Integer; aMenuMode: TKMPopUpMenuMode = pmActOnMouseUp);
begin
inherited Create(aParent, 0, 0, aWidth, 0);
fMenuMode := aMenuMode;
// Note that Shape belongs to us, but we will manually position it to align with Parent to cover it
Shape_Background := TKMShape.Create(Self, 0, 0, aParent.Width, aParent.Height);
Shape_Background.OnClick := MenuHide;
ColumnBox_List := TKMColumnBox.Create(Self, 0, 0, aWidth, 0, fntGrey, bsMenu);
ColumnBox_List.AnchorsStretch;
ColumnBox_List.BackAlpha := 0.8;
ColumnBox_List.Focusable := False;
ColumnBox_List.SetColumns(fntGrey, [''], [0]);
ColumnBox_List.ShowHeader := False;
case fMenuMode of
pmActOnMouseUp: ColumnBox_List.OnClick := MenuActionTriggered;
pmActOnMouseDownNMove: ColumnBox_List.OnChange := MenuActionTriggered;
end;
fMasterControl.SubscribeOnOtherMouseUp(HandleOtherControlMouseUp);
Hide;
end;
procedure TKMPopUpMenu.Clear;
begin
ColumnBox_List.Clear;
end;
function TKMPopUpMenu.GetItemIndex: Integer;
begin
Result := ColumnBox_List.ItemIndex;
end;
function TKMPopUpMenu.GetItemTag(aIndex: Integer): Integer;
begin
Result := ColumnBox_List.Rows[aIndex].Tag;
end;
procedure TKMPopUpMenu.SetItemIndex(aValue: Integer);
begin
ColumnBox_List.ItemIndex := aValue;
end;
procedure TKMPopUpMenu.AddItem(const aCaption: UnicodeString; aTag: Integer = 0);
begin
ColumnBox_List.AddItem(MakeListRow([aCaption], aTag));
// Set own height (and anchored List will follow)
Height := ColumnBox_List.ItemHeight * ColumnBox_List.RowCount;
end;
procedure TKMPopUpMenu.UpdateItem(aIndex: Integer; const aCaption: UnicodeString);
begin
ColumnBox_List.Rows[aIndex].Cells[0].Caption := aCaption;
end;
procedure TKMPopUpMenu.MenuActionTriggered(Sender: TObject);
begin
if Assigned(OnClick) then
OnClick(Self);
if fMenuMode = pmActOnMouseUp then
HideMenu;
end;
procedure TKMPopUpMenu.MenuHide(Sender: TObject);
begin
Hide;
end;
procedure TKMPopUpMenu.HideMenu;
begin
MenuHide(nil);
end;
procedure TKMPopUpMenu.ShowAt(X, Y: Integer);
begin
// Position self (and List will follow)
AbsLeft := X;
AbsTop := Y;
// Actualize modal background position
Shape_Background.AbsLeft := Parent.AbsLeft;
Shape_Background.AbsTop := Parent.AbsTop;
// Reset previously selected item
ColumnBox_List.ItemIndex := -1;
Show;
end;
procedure TKMPopUpMenu.HandleOtherControlMouseUp(Sender: TObject; X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
MouseUp(X, Y, Shift, Button);
end;
procedure TKMPopUpMenu.MouseUp(X, Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
inherited;
Hide;
end;
end.
@@ -0,0 +1,350 @@
unit KM_ControlsProgressBar;
{$I KaM_Remake.inc}
interface
uses
Generics.Collections,
KromOGLUtils,
Classes, Controls,
KM_Controls,
KM_RenderUI,
KM_ResFonts,
KM_Points,
KM_CommonTypes;
type
// Abstract Progress bar
TKMProgressBarAbstract = class abstract(TKMControl)
private
fFont: TKMFont;
fTextAlign: TKMTextAlign;
protected
procedure PaintBar; virtual; abstract;
public
//CaptionLeft and CaptionRight are shown to the left and right from main Caption. Use them only with taCenter
Caption, CaptionLeft, CaptionRight: UnicodeString;
FontColor: TColor4;
TextYOffset: Integer;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont = fntMini);
procedure SetCaptions(const aCaptionLeft, aCaption, aCaptionRight: UnicodeString);
procedure Paint; override;
end;
{Percent bar}
TKMPercentBar = class(TKMProgressBarAbstract)
private
fPosition: Single;
fSeam: Single;
fMainColor: Cardinal;
fAddColor: Cardinal;
procedure SetPosition(aValue: Single);
procedure SetSeam(aValue: Single);
protected
procedure PaintBar; override;
public
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont = fntMini);
property Seam: Single read fSeam write SetSeam;
property Position: Single read fPosition write SetPosition;
property MainColor: Cardinal read fMainColor write fMainColor;
property AddColor: Cardinal read fAddColor write fAddColor;
end;
TKMReplayBar = class(TKMPercentBar)
private
fIsDirty: Boolean; //True is Marks are not not sorted yet
fPosition: Integer;
fPeacetime: Integer;
fMaxValue: Integer;
fHighlightMark: Integer;
fMarks: TList<Integer>;
fMarksPattern: Word;
fOnMarkClick: TIntegerEvent;
fHintResText: Word;
procedure TrySortMarks;
procedure SetPosition(aValue: Integer);
procedure SetPeacetime(aValue: Integer);
procedure SetMaxValue(aValue: Integer);
protected
procedure ControlMouseMove(Sender: TObject; X, Y: Integer; Shift: TShiftState);
procedure PaintBar; override;
public
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont = fntMini); overload;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight, aPosition, aPeacetime, aMaxValue: Integer; aFont: TKMFont = fntMini); overload;
destructor Destroy; override;
property Position: Integer read fPosition;
property Peacetime: Integer read fPeacetime;
property MaxValue: Integer read fMaxValue write SetMaxValue;
procedure SetParameters(aPosition, aPeacetime, aMaxValue: Integer);
property MarksPattern: Word read fMarksPattern write fMarksPattern;
property HintResText: Word read fHintResText write fHintResText;
// property Marks: TList<Integer> read GetMarks;
procedure AddMark(aMark: Integer);
procedure Clear;
property OnMarkClick: TIntegerEvent read fOnMarkClick write fOnMarkClick;
procedure MouseMove(X,Y: Integer; Shift: TShiftState); override;
procedure MouseUp (X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
end;
implementation
uses
SysUtils,
Math,
KM_Resource, KM_ResTexts,
KM_Defaults,
KM_CommonUtils;
{ TKMProgressBarAbstract }
constructor TKMProgressBarAbstract.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont = fntMini);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
fFont := aFont;
FontColor := $FFFFFFFF;
fTextAlign := taCenter;
end;
procedure TKMProgressBarAbstract.SetCaptions(const aCaptionLeft, aCaption, aCaptionRight: UnicodeString);
begin
CaptionLeft := aCaptionLeft;
Caption := aCaption;
CaptionRight := aCaptionRight;
end;
procedure TKMProgressBarAbstract.Paint;
var
captionSize: TKMPoint;
begin
inherited;
PaintBar;
//Now draw text over the bar, if it is required
if Caption <> '' then
begin
//Shadow
TKMRenderUI.WriteText(AbsLeft + 2, (AbsTop + Height div 2)+TextYOffset-4,
Width-4, Caption, fFont, fTextAlign, $FF000000);
//Text
TKMRenderUI.WriteText(AbsLeft + 1, (AbsTop + Height div 2)+TextYOffset-5,
Width-4, Caption, fFont, fTextAlign, FontColor);
end;
if (CaptionLeft <> '') or (CaptionRight <> '') then
captionSize := gRes.Fonts[fFont].GetTextSize(Caption);
if CaptionLeft <> '' then
begin
//Shadow
TKMRenderUI.WriteText(AbsLeft + 2, (AbsTop + Height div 2)+TextYOffset-4,
(Width-4 - captionSize.X) div 2, CaptionLeft, fFont, taRight, $FF000000);
//Text
TKMRenderUI.WriteText(AbsLeft + 1, (AbsTop + Height div 2)+TextYOffset-5,
(Width-4 - captionSize.X) div 2, CaptionLeft, fFont, taRight, FontColor);
end;
if CaptionRight <> '' then
begin
//Shadow
TKMRenderUI.WriteText(AbsLeft + 2 + ((Width-4 + captionSize.X) div 2), (AbsTop + Height div 2)+TextYOffset-4,
(Width-4 - captionSize.X) div 2, CaptionRight, fFont, taLeft, $FF000000);
//Text
TKMRenderUI.WriteText(AbsLeft + 1 + ((Width-4 + captionSize.X) div 2), (AbsTop + Height div 2)+TextYOffset-5,
(Width-4 - captionSize.X) div 2, CaptionRight, fFont, taLeft, FontColor);
end;
end;
{ TKMPercentBar }
constructor TKMPercentBar.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont = fntMini);
begin
inherited;
fMainColor := icBarColorGreen;
fAddColor := icBarColorBlue;
end;
procedure TKMPercentBar.SetPosition(aValue: Single);
begin
fPosition := EnsureRange(aValue, 0, 1);
end;
procedure TKMPercentBar.SetSeam(aValue: Single);
begin
fSeam := EnsureRange(aValue, 0, 1);
end;
procedure TKMPercentBar.PaintBar;
begin
TKMRenderUI.WritePercentBar(AbsLeft, AbsTop, Width, Height, fPosition, fSeam, fMainColor, fAddColor);
end;
{ TKMReplayBar }
constructor TKMReplayBar.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont = fntMini);
begin
Create(aParent, aLeft, aTop, aWidth, aHeight, 0, MaxInt, MaxInt, aFont);
end;
constructor TKMReplayBar.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight, aPosition, aPeacetime, aMaxValue: Integer;
aFont: TKMFont = fntMini);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight, aFont);
SetParameters(aPosition, aPeacetime, aMaxValue);
fMarksPattern := $CF3; //Looks good for 25px height bar
fHighlightMark := -1;
fMarks := TList<Integer>.Create;
aParent.MasterControl.SubscribeOnOtherMouseMove(ControlMouseMove);
end;
destructor TKMReplayBar.Destroy;
begin
FreeAndNil(fMarks);
inherited;
end;
procedure TKMReplayBar.SetParameters(aPosition, aPeacetime, aMaxValue: Integer);
begin
//Apply setters
MaxValue := aMaxValue; //Should be first, since we restrict Position and PT with MaxValue
SetPosition(aPosition);
SetPeacetime(aPeacetime);
end;
procedure TKMReplayBar.AddMark(aMark: Integer);
begin
if Self = nil then Exit;
Assert(fMarks <> nil, 'Marks is not initilized');
fMarks.Add(aMark);
fIsDirty := True;
end;
procedure TKMReplayBar.SetPosition(aValue: Integer);
begin
fPosition := EnsureRange(aValue, 0, fMaxValue);
// Caption := IntToStr(fPosition);
end;
procedure TKMReplayBar.SetPeacetime(aValue: Integer);
begin
fPeacetime := EnsureRange(aValue, 0, MaxInt);
end;
procedure TKMReplayBar.SetMaxValue(aValue: Integer);
begin
fMaxValue := EnsureRange(aValue, 1, MaxInt);
end;
procedure TKMReplayBar.MouseMove(X,Y: Integer; Shift: TShiftState);
const
MAX_DIST_PERCENT = 0.02;
var
pos, bestDist, dist: Integer;
mark, bestMark: Integer;
begin
inherited;
pos := Round((X - AbsLeft) / Width * MaxValue);
bestDist := MaxInt;
bestMark := -1;
TrySortMarks;
for mark in fMarks do
begin
dist := Abs(pos - mark);
if dist < MAX_DIST_PERCENT*fMaxValue then
begin
if dist < bestDist then
begin
bestDist := dist;
bestMark := mark;
end else
Break; //List is sorted, we have found what we need
end;
end;
fHighlightMark := bestMark;
if fHighlightMark <> -1 then
begin
Hint := Format(gResTexts[fHintResText], [TickToTimeStr(fHighlightMark)]);
// Caption := IntToStr(fPosition) + ' (' + IntToStr(fHighlightMark) + ')';
end
else
begin
Hint := '';
// Caption := IntToStr(fPosition);
end;
end;
procedure TKMReplayBar.MouseUp(X: Integer; Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
inherited;
if (fHighlightMark <> -1) and Assigned(fOnMarkClick) then
fOnMarkClick(fHighlightMark);
end;
procedure TKMReplayBar.TrySortMarks;
begin
if fIsDirty then
begin
fIsDirty := False;
fMarks.Sort;
end;
end;
procedure TKMReplayBar.PaintBar;
begin
inherited;
TKMRenderUI.WriteReplayBar(AbsLeft, AbsTop, Width, Height, fPosition, fPeacetime, fMaxValue, fMarks, MarksPattern, fHighlightMark);
end;
procedure TKMReplayBar.Clear;
begin
fMarks.Clear;
end;
procedure TKMReplayBar.ControlMouseMove(Sender: TObject; X, Y: Integer; Shift: TShiftState);
begin
inherited;
if not InRange(X, AbsLeft, AbsRight)
or not InRange(Y, AbsTop, AbsBottom) then
fHighlightMark := -1;
end;
end.
+823
View File
@@ -0,0 +1,823 @@
unit KM_ControlsScroll;
{$I KaM_Remake.inc}
interface
uses
Classes, Controls,
KM_Controls, KM_ControlsBase,
KM_RenderUI,
KM_Points;
type
TKMScrollAxis = (saVertical, saHorizontal);
TKMScrollStyle = (ssGame, ssCommon);
TKMScrollAxisSet = set of TKMScrollAxis;
TKMScrollBar = class(TKMPanel)
private
fScrollAxis: TKMScrollAxis;
fStyle: TKMButtonStyle;
fMinValue: Integer;
fMaxValue: Integer;
fPosition: Integer;
fThumbPos: Integer; //Position of the thumb
fThumbSize: Word; //Length of the thumb
fOffset: Integer;
fScrollDec: TKMButton;
fScrollInc: TKMButton;
fOnChange: TNotifyEvent;
procedure SetMinValue(Value: Integer);
procedure SetMaxValue(Value: Integer);
procedure SetPosition(Value: Integer);
procedure IncPosition(Sender: TObject);
procedure DecPosition(Sender: TObject);
procedure UpdateThumbPos;
procedure UpdateThumbSize;
protected
procedure SetHeight(aValue: Integer); override;
procedure SetWidth(aValue: Integer); override;
procedure SetEnabled(aValue: Boolean); override;
function DoHandleMouseWheelByDefault: Boolean; override;
public
BackAlpha: Single; //Alpha of background (usually 0.5, dropbox 1)
EdgeAlpha: Single; //Alpha of background outline (usually 1)
WheelStep: Word;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aScrollAxis: TKMScrollAxis;
aStyle: TKMButtonStyle; aScrollStyle: TKMScrollStyle = ssGame);
property MinValue: Integer read fMinValue write SetMinValue;
property MaxValue: Integer read fMaxValue write SetMaxValue;
property Position: Integer read fPosition write SetPosition;
procedure MouseDown(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
procedure MouseMove(X,Y: Integer; Shift: TShiftState); override;
procedure MouseWheel(Sender: TObject; WheelSteps: Integer; var aHandled: Boolean); override;
property OnChange: TNotifyEvent read fOnChange write fOnChange;
procedure Paint; override;
end;
TKMScrollPanel = class(TKMPanel)
private
fChildsPanel: TKMPanel;
fClipRect: TKMRect;
fScrollBarH: TKMScrollBar;
fScrollBarV: TKMScrollBar;
fScrollAxisSet: TKMScrollAxisSet;
fPadding: TKMRect;
fScrollV_PadTop: Integer;
fScrollV_PadBottom: Integer;
fScrollV_PadLeft: Integer;
procedure UpdateScrolls(Sender: TObject; aValue: Boolean); overload;
procedure UpdateScrolls(Sender: TObject); overload;
procedure UpdateScrollV; overload;
procedure UpdateScrollV(aChildsRect: TKMRect); overload;
procedure UpdateScrollV(Sender: TObject; const aValue: Integer); overload;
procedure UpdateScrollH; overload;
procedure UpdateScrollH(aChildsRect: TKMRect); overload;
procedure UpdateScrollH(Sender: TObject; const aValue: Integer); overload;
procedure ScrollChanged(Sender: TObject);
function GetChildsRect: TKMRect;
function AllowScrollV: Boolean;
function AllowScrollH: Boolean;
procedure SetScrollVPadTop(const Value: Integer);
procedure SetScrollVPadBottom(const Value: Integer);
procedure SetScrollVPadLeft(const Value: Integer);
protected
procedure SetVisible(aValue: Boolean); override;
procedure SetHitable(const aValue: Boolean); override;
procedure SetLeft(aValue: Integer); override;
procedure SetTop(aValue: Integer); override;
procedure SetHeight(aValue: Integer); override;
procedure SetWidth(aValue: Integer); override;
function GetDrawRect: TKMRect; override;
function GetAbsDrawLeft: Integer; override;
function GetAbsDrawTop: Integer; override;
function GetAbsDrawRight: Integer; override;
function GetAbsDrawBottom: Integer; override;
procedure UpdateVisibility; override;
public
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aScrollAxisSet: TKMScrollAxisSet;
aStyle: TKMButtonStyle; aScrollStyle: TKMScrollStyle);
property ScrollH: TKMScrollBar read fScrollBarH;
property ScrollV: TKMScrollBar read fScrollBarV;
function AddChild(aChild: TKMControl): Integer; override;
property ClipRect: TKMRect read fClipRect;
property Padding: TKMRect read fPadding write fPadding;
property ScrollV_PadTop: Integer read fScrollV_PadTop write SetScrollVPadTop;
property ScrollV_PadLeft: Integer read fScrollV_PadLeft write SetScrollVPadLeft;
property ScrollV_PadBottom: Integer read fScrollV_PadBottom write SetScrollVPadBottom;
procedure Show; override;
procedure MouseWheel(Sender: TObject; WheelSteps: Integer; var aHandled: Boolean); override;
procedure ClipY;
procedure UnClipY;
procedure UpdateScrolls; overload;
procedure Paint; override;
end;
implementation
uses
Math,
KM_ResTypes;
{ TKMScrollBar }
constructor TKMScrollBar.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aScrollAxis: TKMScrollAxis;
aStyle: TKMButtonStyle; aScrollStyle: TKMScrollStyle = ssGame);
var
decId, incId: Integer;
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
BackAlpha := 0.5;
EdgeAlpha := 0.75;
fScrollAxis := aScrollAxis;
fMinValue := 0;
fMaxValue := 10;
fPosition := 0;
fStyle := aStyle;
WheelStep := 1;
if aScrollAxis = saVertical then
begin
fScrollDec := TKMButton.Create(Self, 0, 0, aWidth, aWidth, 591, rxGui, aStyle);
fScrollInc := TKMButton.Create(Self, 0, aHeight-aWidth, aWidth, aWidth, 590, rxGui, aStyle);
fScrollDec.Anchors := [anLeft, anTop, anRight];
fScrollInc.Anchors := [anLeft, anRight, anBottom];
end;
if aScrollAxis = saHorizontal then
begin
if aScrollStyle = ssGame then
begin
decId := 2;
incId := 3;
end else begin
decId := 674;
incId := 675;
end;
fScrollDec := TKMButton.Create(Self, 0, 0, aHeight, aHeight, decId, rxGui, aStyle);
fScrollInc := TKMButton.Create(Self, aWidth-aHeight, 0, aHeight, aHeight, incId, rxGui, aStyle);
fScrollDec.Anchors := [anLeft, anTop, anBottom];
fScrollInc.Anchors := [anTop, anRight, anBottom];
end;
fScrollDec.OnClick := DecPosition;
fScrollDec.OnMouseWheel := MouseWheel;
fScrollInc.OnClick := IncPosition;
fScrollInc.OnMouseWheel := MouseWheel;
UpdateThumbSize;
end;
procedure TKMScrollBar.SetHeight(aValue: Integer);
begin
inherited;
//Update Thumb size
UpdateThumbSize;
end;
procedure TKMScrollBar.SetWidth(aValue: Integer);
begin
inherited;
//Update Thumb size
UpdateThumbSize;
end;
procedure TKMScrollBar.SetEnabled(aValue: Boolean);
begin
inherited;
fScrollDec.Enabled := Enabled;
fScrollInc.Enabled := Enabled;
end;
procedure TKMScrollBar.SetMinValue(Value: Integer);
begin
fMinValue := Max(0, Value);
Enabled := (fMaxValue > fMinValue);
SetPosition(fPosition);
end;
procedure TKMScrollBar.SetMaxValue(Value: Integer);
begin
fMaxValue := Max(0, Value);
Enabled := (fMaxValue > fMinValue);
SetPosition(fPosition);
end;
procedure TKMScrollBar.SetPosition(Value: Integer);
begin
fPosition := EnsureRange(Value, fMinValue, fMaxValue);
UpdateThumbPos;
end;
procedure TKMScrollBar.IncPosition(Sender: TObject);
begin
SetPosition(fPosition + WheelStep);
if Assigned(fOnChange) then
fOnChange(Self);
end;
procedure TKMScrollBar.DecPosition(Sender: TObject);
begin
SetPosition(fPosition - WheelStep);
if Assigned(fOnChange) then
fOnChange(Self);
end;
procedure TKMScrollBar.UpdateThumbPos;
begin
fThumbPos := 0;
if fMaxValue > fMinValue then
case fScrollAxis of
saVertical: fThumbPos := (fPosition-fMinValue)*(Height-Width*2-fThumbSize) div (fMaxValue-fMinValue);
saHorizontal: fThumbPos := (fPosition-fMinValue)*(Width-Height*2-fThumbSize) div (fMaxValue-fMinValue);
end
else
case fScrollAxis of
saVertical: fThumbPos := Math.max((Height-Width*2-fThumbSize),0) div 2;
saHorizontal: fThumbPos := Math.max((Width-Height*2-fThumbSize),0) div 2;
end;
end;
procedure TKMScrollBar.UpdateThumbSize;
begin
case fScrollAxis of
saVertical: fThumbSize := Math.max(0, (Height-2*Width)) div 4;
saHorizontal: fThumbSize := Math.max(0, (Width-2*Height)) div 4;
end;
//If size has changed, then Pos needs to be updated as well (depends on it)
UpdateThumbPos;
end;
procedure TKMScrollBar.MouseDown(X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
var
T: Integer;
begin
inherited;
fOffset := 0;
case fScrollAxis of
saVertical: begin
T := Y - AbsTop - Width - fThumbPos;
if InRange(T, 0, fThumbSize) then
fOffset := T - fThumbSize div 2;
end;
saHorizontal: begin
T := X - AbsLeft - Height - fThumbPos;
if InRange(T, 0, fThumbSize) then
fOffset := T - fThumbSize div 2;
end;
end;
MouseMove(X,Y,Shift); //Will change Position and call OnChange event
end;
procedure TKMScrollBar.MouseMove(X,Y: Integer; Shift: TShiftState);
var
newPos: Integer;
T: Integer;
begin
inherited;
if not (ssLeft in Shift) then Exit;
newPos := fPosition;
case fScrollAxis of
saVertical:
begin
T := Y - fOffset - AbsTop - Width;
if InRange(T, 0, Height - Width * 2) then
newPos := Round(fMinValue+((T - fThumbSize / 2) / (Height-Width*2-fThumbSize)) * (fMaxValue - fMinValue) );
end;
saHorizontal:
begin
T := X - fOffset - AbsLeft - Height;
if InRange(T, 0, Width - Height * 2) then
newPos := Round(fMinValue+((T - fThumbSize / 2) / (Width-Height*2-fThumbSize)) * (fMaxValue - fMinValue) );
end;
end;
if newPos <> fPosition then
begin
SetPosition(newPos);
if Assigned(fOnChange) then
fOnChange(Self);
end;
end;
function TKMScrollBar.DoHandleMouseWheelByDefault: Boolean;
begin
Result := False;
end;
procedure TKMScrollBar.MouseWheel(Sender: TObject; WheelSteps: Integer; var aHandled: Boolean);
begin
inherited;
if aHandled then Exit;
aHandled := WheelSteps <> 0;
if aHandled then
begin
Position := Position - WheelStep * WheelSteps;
if Assigned(fOnChange) then
fOnChange(Self);
end;
end;
procedure TKMScrollBar.Paint;
var
buttonState: TKMButtonStateSet;
begin
inherited;
case fScrollAxis of
saVertical: TKMRenderUI.WriteBevel(AbsLeft, AbsTop+Width, Width, Height - Width*2, EdgeAlpha, BackAlpha);
saHorizontal: TKMRenderUI.WriteBevel(AbsLeft+Height, AbsTop, Width - Height*2, Height, EdgeAlpha, BackAlpha);
end;
if fMaxValue > fMinValue then
buttonState := []
else
buttonState := [bsDisabled];
if not (bsDisabled in buttonState) then //Only show thumb when usable
case fScrollAxis of
saVertical: TKMRenderUI.Write3DButton(AbsLeft,AbsTop+Width+fThumbPos,Width,fThumbSize,rxGui,0,$FFFF00FF,buttonState,fStyle);
saHorizontal: TKMRenderUI.Write3DButton(AbsLeft+Height+fThumbPos,AbsTop,fThumbSize,Height,rxGui,0,$FFFF00FF,buttonState,fStyle);
end;
end;
{ TKMScrollPanel }
constructor TKMScrollPanel.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aScrollAxisSet: TKMScrollAxisSet;
aStyle: TKMButtonStyle; aScrollStyle: TKMScrollStyle);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
fPadding := KMRect(0, 0, 0, 0);
fScrollAxisSet := aScrollAxisSet;
fChildsPanel := TKMPanel.Create(Self, aLeft, aTop, aWidth, aHeight);
fChildsPanel.AnchorsStretch;
fChildsPanel.IsHitTestUseDrawRect := True; // We want DrawRect to be used on the ScrollPanel
// We should create scroll bars on Parent panel, since we move panel there and back and scroll should not be scrolled as well
fScrollBarH := TKMScrollBar.Create(aParent, aLeft, aTop + aHeight - 20, aWidth, 20, saHorizontal, aStyle, aScrollStyle);
fScrollBarH.Hide;
fScrollBarH.OnChange := ScrollChanged;
fScrollBarH.WheelStep := 10;
fScrollBarV := TKMScrollBar.Create(aParent, aLeft + aWidth - 20, aTop, 20, aHeight, saVertical, aStyle, aScrollStyle);
fScrollBarV.Hide;
fScrollBarV.OnChange := ScrollChanged;
fScrollBarV.WheelStep := 10;
IsHitTestUseDrawRect := True; // We want DrawRect to be used on the ScrollPanel
fScrollV_PadTop := 0;
fScrollV_PadBottom := 0;
fClipRect := KMRect(Left, Top, Right, Bottom);
end;
function TKMScrollPanel.AddChild(aChild: TKMControl): Integer;
begin
aChild.IsHitTestUseDrawRect := True;
if fChildsPanel = nil then
Result := inherited AddChild(aChild)
else
begin
Result := fChildsPanel.AddChild(aChild);
aChild.OnHeightChange := UpdateScrollV;
aChild.OnWidthChange := UpdateScrollH;
aChild.OnPositionSet := UpdateScrolls;
aChild.OnChangeVisibility := UpdateScrolls;
aChild.OnChangeEnableStatus := UpdateScrolls;
end;
end;
//Update View is usefull when use Padding Left or Top
//To show panel properly initially. Beeter to invoke from Show Element
procedure TKMScrollPanel.UpdateVisibility;
begin
inherited;
if Visible
and ((fPadding.Left > 0) or (fPadding.Top > 0)) then //Update visibility only when left/top padding > 0
begin
if saHorizontal in fScrollAxisSet then
begin
fScrollBarH.Position := fPadding.Left;
ScrollChanged(fScrollBarH);
end;
if saVertical in fScrollAxisSet then
begin
fScrollBarV.Position := fPadding.Top;
ScrollChanged(fScrollBarV);
end;
end;
end;
procedure TKMScrollPanel.MouseWheel(Sender: TObject; WheelSteps: Integer; var aHandled: Boolean);
begin
if (saVertical in fScrollAxisSet) and fScrollBarV.Visible then
fScrollBarV.MouseWheel(Sender, WheelSteps, aHandled)
else if (saHorizontal in fScrollAxisSet) and fScrollBarH.Visible then
fScrollBarH.MouseWheel(Sender, WheelSteps, aHandled)
else
inherited;
end;
procedure TKMScrollPanel.ScrollChanged(Sender: TObject);
var
oldValue: Integer;
begin
if Sender = fScrollBarH then
begin
oldValue := Left;
Left := fClipRect.Left - fScrollBarH.Position + fPadding.Left;
//To compensate changes in SetLeft
Inc(fClipRect.Left, oldValue - Left);
Inc(fClipRect.Right, oldValue - Left);
end else
if Sender = fScrollBarV then
begin
oldValue := Top;
Top := fClipRect.Top - fScrollBarV.Position + fPadding.Top;
//To compensate changes in SetTop
Inc(fClipRect.Top, oldValue - Top);
Inc(fClipRect.Bottom, oldValue - Top);
end;
end;
procedure TKMScrollPanel.UpdateScrolls(Sender: TObject; aValue: Boolean);
begin
// No need to update scrolls if panel is not visible
// In case we've just hide panel then DrawRect is RECT_ZERO and scrools will be set to 0 position
if not Visible then Exit;
if (Sender <> fScrollBarH) and (Sender <> fScrollBarV) then
UpdateScrolls(nil);
end;
procedure TKMScrollPanel.UpdateScrollH(Sender: TObject; const aValue: Integer);
begin
if (Sender <> fScrollBarH) then
UpdateScrollH;
end;
procedure TKMScrollPanel.UpdateScrollH;
begin
if not (saHorizontal in fScrollAxisSet) then Exit;
UpdateScrollH(GetChildsRect);
end;
procedure TKMScrollPanel.UpdateScrollH(aChildsRect: TKMRect);
var
newPos: Integer;
showScroll: Boolean;
begin
if not (saHorizontal in fScrollAxisSet) then Exit;
showScroll := False;
if aChildsRect.Width + fPadding.Left + fPadding.Right > fClipRect.Width then
begin
fScrollBarH.MaxValue := aChildsRect.Width - fClipRect.Width + fPadding.Left + fPadding.Right;
newPos := fClipRect.Left - Left;
if newPos > fScrollBarH.MaxValue then
SetLeftSilently(Left + newPos - fScrollBarH.MaxValue); //Slightly move panel to the top, when resize near maxvalue position
showScroll := True;
end else begin
fScrollBarH.Position := 0;
if Left <> fClipRect.Left then
SetLeftSilently(fClipRect.Left); //Set directrly to avoid SetLeft call
end;
fScrollBarH.Width := Width;
if showScroll <> fScrollBarH.Visible then
begin
fScrollBarH.Visible := showScroll;
fChildsPanel.Height := Height - 20*Byte(showScroll);
end;
end;
procedure TKMScrollPanel.UpdateScrollV(Sender: TObject; const aValue: Integer);
begin
if (Sender <> fScrollBarV) then
UpdateScrollV;
end;
procedure TKMScrollPanel.UpdateScrollV;
begin
if not (saVertical in fScrollAxisSet) then Exit;
UpdateScrollV(GetChildsRect);
end;
procedure TKMScrollPanel.UpdateScrollV(aChildsRect: TKMRect);
var
newPos: Integer;
showScroll: Boolean;
begin
if not (saVertical in fScrollAxisSet) then Exit;
//Do not set Visible, avoid trigger OnChangeVisibility
showScroll := False;
if aChildsRect.Height + fPadding.Top + fPadding.Bottom > fClipRect.Height then
begin
fScrollBarV.MaxValue := aChildsRect.Height - fClipRect.Height + fPadding.Top + fPadding.Bottom;
newPos := fClipRect.Top - Top;
if newPos > fScrollBarV.MaxValue then
SetTopSilently(Top + newPos - fScrollBarV.MaxValue); //Slightly move panel to the top, when resize near maxvalue position
showScroll := True;
end else begin
fScrollBarV.Position := 0;
if Top <> fClipRect.Top then
SetTopSilently(fClipRect.Top); //Set directrly to avoid SetTop call
end;
fScrollBarV.Height := Height - fScrollV_PadTop - fScrollV_PadBottom;
if showScroll <> fScrollBarV.Visible then
begin
fScrollBarV.Visible := showScroll;
fChildsPanel.Width := Width - 20*Byte(showScroll);
end;
end;
procedure TKMScrollPanel.UpdateScrolls;
begin
UpdateScrolls(nil);
end;
procedure TKMScrollPanel.UpdateScrolls(Sender: TObject);
var
childsRect: TKMRect;
begin
childsRect := GetChildsRect;
UpdateScrollV(childsRect);
UpdateScrollH(childsRect);
end;
procedure TKMScrollPanel.SetLeft(aValue: Integer);
var
oldValue: Integer;
begin
oldValue := Left;
inherited;
Inc(fClipRect.Left, Left - oldValue);
Inc(fClipRect.Right, Left - oldValue);
UpdateScrolls(nil);
end;
procedure TKMScrollPanel.SetScrollVPadBottom(const Value: Integer);
begin
fScrollV_PadBottom := Value;
fScrollBarV.Height := fScrollBarV.Height - Value;
end;
procedure TKMScrollPanel.SetScrollVPadLeft(const Value: Integer);
begin
fScrollV_PadLeft := Value;
end;
procedure TKMScrollPanel.SetScrollVPadTop(const Value: Integer);
begin
fScrollV_PadTop := Value;
fScrollBarV.Top := fScrollBarV.Top + Value;
fScrollBarV.Height := fScrollBarV.Height - Value;
end;
procedure TKMScrollPanel.SetTop(aValue: Integer);
var
oldValue: Integer;
begin
oldValue := Top;
inherited;
Inc(fClipRect.Top, Top - oldValue);
Inc(fClipRect.Bottom, Top - oldValue);
UpdateScrolls(nil);
end;
procedure TKMScrollPanel.SetVisible(aValue: Boolean);
begin
inherited;
// Hide ScrollBars with if we hide ScrollPanel
if not aValue then
begin
fScrollBarH.Visible := aValue;
fScrollBarV.Visible := aValue;
end;
end;
procedure TKMScrollPanel.SetWidth(aValue: Integer);
var
oldValue: Integer;
begin
oldValue := Width;
inherited;
Inc(fClipRect.Right, Width - oldValue);
fScrollBarV.Left := fClipRect.Left + Width + fScrollV_PadLeft;
UpdateScrolls(nil);
end;
procedure TKMScrollPanel.Show;
begin
inherited;
// We can use Visible here, since Self is he parent of ChildsPanel
fChildsPanel.Visible := True;
end;
procedure TKMScrollPanel.SetHeight(aValue: Integer);
var
oldValue: Integer;
begin
oldValue := Height;
inherited;
Inc(fClipRect.Bottom, Height - oldValue);
fScrollBarH.Top := fClipRect.Top + Height;
UpdateScrolls(nil);
end;
procedure TKMScrollPanel.SetHitable(const aValue: Boolean);
begin
inherited;
fChildsPanel.Hitable := aValue;
end;
function TKMScrollPanel.GetChildsRect: TKMRect;
var
I: Integer;
begin
Result := KMRECT_ZERO; //Zero rect, by default
for I := 0 to fChildsPanel.ChildCount - 1 do
begin
if not fChildsPanel.Childs[I].IsPainted then
Continue;
if fChildsPanel.Childs[I].Left < Result.Left then
Result.Left := fChildsPanel.Childs[I].Left;
if fChildsPanel.Childs[I].Top < Result.Top then
Result.Top := fChildsPanel.Childs[I].Top;
if fChildsPanel.Childs[I].Right > Result.Right then
Result.Right := fChildsPanel.Childs[I].Right;
if fChildsPanel.Childs[I].Bottom > Result.Bottom then
Result.Bottom := fChildsPanel.Childs[I].Bottom;
end;
end;
function TKMScrollPanel.AllowScrollV: Boolean;
begin
Result := saVertical in fScrollAxisSet;
end;
function TKMScrollPanel.AllowScrollH: Boolean;
begin
Result := saHorizontal in fScrollAxisSet;
end;
function TKMScrollPanel.GetDrawRect: TKMRect;
begin
Result := KMRect(AbsDrawLeft, AbsDrawTop, AbsDrawRight, AbsDrawBottom);
end;
function TKMScrollPanel.GetAbsDrawLeft: Integer;
begin
Result := Parent.AbsLeft + fClipRect.Left;
end;
function TKMScrollPanel.GetAbsDrawTop: Integer;
begin
Result := Parent.AbsTop + fClipRect.Top;
end;
function TKMScrollPanel.GetAbsDrawRight: Integer;
begin
Result := Parent.AbsLeft + fClipRect.Right + 20*Byte(AllowScrollV and not fScrollBarV.Visible);
end;
function TKMScrollPanel.GetAbsDrawBottom: Integer;
begin
Result := Parent.AbsTop + fClipRect.Bottom + 20*Byte(AllowScrollH and not fScrollBarH.Visible);
end;
procedure TKMScrollPanel.Paint;
begin
TKMRenderUI.SetupClipX(AbsDrawLeft, AbsDrawRight);
TKMRenderUI.SetupClipY(AbsDrawTop, AbsDrawBottom);
inherited;
TKMRenderUI.ReleaseClipY;
TKMRenderUI.ReleaseClipX;
end;
procedure TKMScrollPanel.ClipY;
begin
TKMRenderUI.SetupClipY(AbsDrawTop, AbsDrawBottom);
end;
procedure TKMScrollPanel.UnClipY;
begin
TKMRenderUI.ReleaseClipY;
end;
end.
+455
View File
@@ -0,0 +1,455 @@
unit KM_ControlsSwitch;
{$I KaM_Remake.inc}
interface
uses
Classes, Controls,
KromOGLUtils,
KM_Controls,
KM_ResFonts;
type
TKMCheckBoxState = (cbsUnchecked, cbsSemiChecked, cbsChecked);
{ Checkbox }
TKMCheckBox = class(TKMControl)
private
fCaption: UnicodeString;
fState: TKMCheckBoxState;
fHasSemiState: Boolean;
fFont: TKMFont;
function GetCheckedBool: Boolean;
public
DrawOutline: Boolean;
LineColor: TColor4; //color of outline
LineWidth: Byte;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; const aCaption: UnicodeString;
aFont: TKMFont; aHasSemiState: Boolean = False); overload;
property Caption: UnicodeString read fCaption write fCaption;
procedure SetChecked(aChecked: Boolean);
property Checked: Boolean read GetCheckedBool write SetChecked;
property CheckState: TKMCheckBoxState read fState write fState;
function IsSemiChecked: Boolean;
procedure Check;
procedure Uncheck;
procedure SemiCheck;
procedure SwitchCheck(aForward: Boolean = True);
procedure MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
procedure Paint; override;
end;
TKMRadioGroupItem = record
Text: UnicodeString;
Hint: UnicodeString;
Enabled: Boolean;
Visible: Boolean;
end;
{ TKMRadioGroup }
TKMRadioGroup = class(TKMControl)
private
fItemIndex: Integer;
fCount: Integer;
fItems: array of TKMRadioGroupItem;
fFont: TKMFont;
fMouseOverItem: SmallInt;
fOnChange: TNotifyEvent;
procedure UpdateMouseOverPositions(X,Y: Integer);
function GetItem(aIndex: Integer): TKMRadioGroupItem;
function GetItemIndexByRow(aRowIndex: Integer): Integer;
function GetVisibleCount: Integer;
function GetLineHeight: Single;
function GetIsSelected: Boolean;
protected
function GetHint: UnicodeString; override;
public
AllowUncheck: Boolean; //Do we allow to uncheck selected element ? Usually not, but its possible
DrawChkboxOutline: Boolean;
LineColor: TColor4; //color of outline
LineWidth: Byte;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont);
procedure Add(const aText: String; aEnabled: Boolean = True); overload;
procedure Add(const aText: String; aEnabled, aVisible: Boolean); overload;
procedure Add(const aText, aHint: String; aEnabled: Boolean = True; aVisible: Boolean = True); overload;
procedure Clear;
property Count: Integer read fCount;
property IsSelected: Boolean read GetIsSelected;
property VisibleCount: Integer read GetVisibleCount;
property ItemIndex: Integer read fItemIndex write fItemIndex;
property OnChange: TNotifyEvent read fOnChange write fOnChange;
property Item[aIndex: Integer]: TKMRadioGroupItem read GetItem;
procedure SetItemEnabled(aIndex: Integer; aEnabled: Boolean; aUncheckDisabled: Boolean = True);
procedure SetItemVisible(aIndex: Integer; aEnabled: Boolean);
procedure CheckFirstCheckable;
property LineHeight: Single read GetLineHeight;
procedure MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
procedure MouseMove(X,Y: Integer; Shift: TShiftState); override;
procedure Paint; override;
end;
implementation
uses
SysUtils, Math,
KM_RenderUI,
KM_Resource,
KM_Defaults;
{ TKMCheckBox }
constructor TKMCheckBox.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; const aCaption: UnicodeString;
aFont: TKMFont; aHasSemiState: Boolean = False);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
fFont := aFont;
fCaption := aCaption;
fHasSemiState := aHasSemiState;
LineWidth := 1;
LineColor := clChkboxOutline;
end;
procedure TKMCheckBox.SetChecked(aChecked: Boolean);
begin
if aChecked then
fState := cbsChecked
else
fState := cbsUnchecked;
end;
function TKMCheckBox.GetCheckedBool: Boolean;
begin
Result := fState = cbsChecked;
end;
function TKMCheckBox.IsSemiChecked: Boolean;
begin
Result := fState = cbsSemiChecked;
end;
procedure TKMCheckBox.Check;
begin
fState := cbsChecked;
end;
procedure TKMCheckBox.SemiCheck;
begin
fState := cbsSemiChecked;
end;
procedure TKMCheckBox.Uncheck;
begin
fState := cbsUnchecked;
end;
procedure TKMCheckBox.SwitchCheck(aForward: Boolean = True);
begin
if fHasSemiState then
fState := TKMCheckBoxState((Byte(fState) + 3 + 2*Byte(aForward) - 1) mod 3)
else
begin
if Checked then
Uncheck
else
Check;
end;
end;
procedure TKMCheckBox.MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
if (csDown in State) and (Button = mbLeft) then
case fState of
cbsSemiChecked,
cbsUnchecked: Check; //Let's assume we prefer check for now
cbsChecked: Uncheck;
end;
inherited; //There are OnMouseUp and OnClick events there
end;
//We can replace it with something better later on. For now [x] fits just fine
//Might need additional graphics to be added to gui.rx
//Some kind of box with an outline, darkened background and shadow maybe, similar to other controls.
procedure TKMCheckBox.Paint;
var
col, semiCol: TColor4;
checkSize: Integer;
begin
inherited;
if Enabled then
begin
col := icWhite;
semiCol := $FFCCCCCC;
end
else
begin
col := icGray2;
semiCol := $FF888888;
end;
checkSize := gRes.Fonts[fFont].GetTextSize('x').Y + 1;
TKMRenderUI.WriteBevel(AbsLeft, AbsTop, checkSize - 4, checkSize-4, 1, Byte(not IsSemiChecked and Enabled)*0.35);
if DrawOutline then
TKMRenderUI.WriteOutline(AbsLeft, AbsTop, checkSize - 4, checkSize - 4, LineWidth, LineColor);
case fState of
cbsChecked: TKMRenderUI.WriteText(AbsLeft + (checkSize-4) div 2, AbsTop - 1, 0, 'x', fFont, taCenter, col);
cbsSemiChecked: TKMRenderUI.WriteText(AbsLeft + (checkSize-4) div 2, AbsTop - 1, 0, 'x', fFont, taCenter, semiCol);
cbsUnchecked: ; //Do not draw anything
end;
TKMRenderUI.WriteText(AbsLeft + checkSize, AbsTop, Width - checkSize, fCaption, fFont, taLeft, col);
end;
{ TKMRadioGroup }
constructor TKMRadioGroup.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aFont: TKMFont);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
fFont := aFont;
fItemIndex := -1;
LineWidth := 1;
LineColor := clChkboxOutline;
AllowUncheck := False;
end;
procedure TKMRadioGroup.Add(const aText: String; aEnabled: Boolean);
begin
Add(aText, '', aEnabled);
end;
procedure TKMRadioGroup.Add(const aText: String; aEnabled, aVisible: Boolean);
begin
Add(aText, '', aEnabled, aVisible);
end;
procedure TKMRadioGroup.Add(const aText, aHint: String; aEnabled: Boolean = True; aVisible: Boolean = True);
begin
if fCount >= Length(fItems) then
SetLength(fItems, fCount + 8);
fItems[fCount].Text := aText;
fItems[fCount].Hint := aHint;
fItems[fCount].Enabled := aEnabled;
fItems[fCount].Visible := aVisible;
Inc(fCount);
end;
procedure TKMRadioGroup.Clear;
begin
fCount := 0;
end;
procedure TKMRadioGroup.MouseUp(X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
var
changed: Boolean;
begin
if (csDown in State) and (Button = mbLeft) then
begin
UpdateMouseOverPositions(X,Y);
if (fMouseOverItem <> -1) and fItems[fMouseOverItem].Enabled then
begin
changed := False;
if (fMouseOverItem = fItemIndex) then
begin
if AllowUncheck then
begin
fItemIndex := -1; //Uncheck
changed := True;
end;
end else begin
fItemIndex := fMouseOverItem;
changed := True;
end;
if changed and Assigned(fOnChange) then
begin
fOnChange(Self);
Exit; //Don't generate OnClick after OnChanged event (esp. when reloading Game on local change)
end;
end;
end;
inherited; //There are OnMouseUp and OnClick events there
end;
procedure TKMRadioGroup.MouseMove(X,Y: Integer; Shift: TShiftState);
begin
inherited;
UpdateMouseOverPositions(X,Y);
end;
procedure TKMRadioGroup.UpdateMouseOverPositions(X,Y: Integer);
var
mouseOverRow, itemIndex: Integer;
begin
fMouseOverItem := -1;
if InRange(Y, AbsTop, AbsTop + Height) and (LineHeight > 0) then
begin
mouseOverRow := EnsureRange((Y - AbsTop) div Round(LineHeight), 0, VisibleCount - 1);
itemIndex := GetItemIndexByRow(mouseOverRow);
if (itemIndex <> -1) and InRange(X, AbsLeft, AbsLeft + LineHeight + gRes.Fonts[fFont].GetTextSize(fItems[itemIndex].Text).X) then
fMouseOverItem := itemIndex;
end;
end;
function TKMRadioGroup.GetItem(aIndex: Integer): TKMRadioGroupItem;
begin
Assert(aIndex < fCount, 'Can''t get radio group item for index ' + IntToStr(aIndex));
Result := fItems[aIndex];
end;
function TKMRadioGroup.GetItemIndexByRow(aRowIndex: Integer): Integer;
var
I, K: Integer;
begin
Assert(aRowIndex < VisibleCount, 'GetItemByRow: aRowIndex >= VisibleCount');
K := 0;
Result := -1;
for I := 0 to fCount - 1 do
begin
if not fItems[I].Visible then Continue;
if aRowIndex = K then
begin
Result := I;
Exit;
end;
Inc(K);
end;
end;
function TKMRadioGroup.GetVisibleCount: Integer;
var
I: Integer;
begin
Result := 0;
for I := 0 to fCount - 1 do
if fItems[I].Visible then
Inc(Result);
end;
function TKMRadioGroup.GetLineHeight: Single;
begin
Result := IfThen(VisibleCount > 0, Height / VisibleCount, Height);
end;
function TKMRadioGroup.GetIsSelected: Boolean;
begin
Result := fItemIndex <> -1;
end;
procedure TKMRadioGroup.SetItemEnabled(aIndex: Integer; aEnabled: Boolean; aUncheckDisabled: Boolean = True);
begin
Assert(aIndex < fCount, 'Can''t SetItemEnabled for index ' + IntToStr(aIndex));
fItems[aIndex].Enabled := aEnabled;
// Reset item index, if this item was selected
if aUncheckDisabled and not aEnabled and (fItemIndex = aIndex) then
fItemIndex := -1;
end;
procedure TKMRadioGroup.SetItemVisible(aIndex: Integer; aEnabled: Boolean);
begin
Assert(aIndex < fCount, 'Can''t SetItemVisible for index ' + IntToStr(aIndex));
fItems[aIndex].Visible := aEnabled;
end;
procedure TKMRadioGroup.CheckFirstCheckable;
var
I: Integer;
begin
if fItemIndex <> -1 then Exit;
for I := 0 to fCount - 1 do
if fItems[I].Enabled and fItems[I].Visible then
begin
fItemIndex := I;
Exit;
end;
end;
function TKMRadioGroup.GetHint: UnicodeString;
begin
Result := inherited GetHint;
if Result = '' then
begin
if fMouseOverItem <> -1 then
Result := fItems[fMouseOverItem].Hint;
end;
end;
//We can replace it with something better later on. For now [x] fits just fine
//Might need additional graphics to be added to gui.rx
//Some kind of box with an outline, darkened background and shadow maybe, similar to other controls.
procedure TKMRadioGroup.Paint;
const
FONT_COL: array [Boolean] of TColor4 = ($FF888888, $FFFFFFFF);
var
CheckSize: Integer;
I, VisibleI: Integer;
begin
inherited;
if (Count = 0) or (VisibleCount = 0) then Exit; //Avoid dividing by zero
CheckSize := gRes.Fonts[fFont].GetTextSize('x').Y + 1;
VisibleI := 0;
for I := 0 to Count - 1 do
begin
if not fItems[I].Visible then
Continue;
TKMRenderUI.WriteBevel(AbsLeft, AbsTop + Round(VisibleI * LineHeight), CheckSize-4, CheckSize-4, 1, 0.3);
if DrawChkboxOutline then
TKMRenderUI.WriteOutline(AbsLeft, AbsTop + Round(VisibleI * LineHeight), CheckSize-4, CheckSize-4, LineWidth, LineColor);
if fItemIndex = I then
TKMRenderUI.WriteText(AbsLeft + (CheckSize - 4) div 2, AbsTop + Round(VisibleI * LineHeight) - 1, 0,
'x', fFont, taCenter, FONT_COL[Enabled and fItems[I].Enabled]);
// Caption
TKMRenderUI.WriteText(AbsLeft + CheckSize, AbsTop + Round(VisibleI * LineHeight), Width - Round(LineHeight),
fItems[I].Text, fFont, taLeft, FONT_COL[Enabled and fItems[I].Enabled]);
Inc(VisibleI);
end;
end;
end.
+266
View File
@@ -0,0 +1,266 @@
unit KM_ControlsTrackBar;
{$I KaM_Remake.inc}
interface
uses
Classes, Controls,
KM_Controls,
KM_ResFonts,
KM_Points;
type
TKMTrackBar = class(TKMControl)
private
fTrackTop: Byte; //Offset trackbar from top (if Caption <> '')
fTrackHeight: Byte; //Trackbar height
fMinValue: Word;
fMaxValue: Word;
fOnChange: TNotifyEvent;
fCaption: UnicodeString;
fPosition: Word;
fFont: TKMFont;
fRange: TKMRangeInt;
fThumbText: UnicodeString;
fAutoThumbWidth: Boolean;
fFixedThumbWidth: Boolean;
procedure SetCaption(const aValue: UnicodeString);
procedure SetPosition(aValue: Word);
procedure SetRange(const aRange: TKMRangeInt);
procedure UpdateThumbWidth;
procedure SetThumbText(const Value: UnicodeString);
procedure SetAutoThumbWidth(const aValue: Boolean);
protected
function DoHandleMouseWheelByDefault: Boolean; override;
public
Step: Byte; //Change Position by this amount each time
ThumbWidth: Word;
CaptionWidth: Integer;
SliderFont: TKMFont;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth: Integer; aMin, aMax: Word);
property Caption: UnicodeString read fCaption write SetCaption;
property Position: Word read fPosition write SetPosition;
property Range: TKMRangeInt read fRange write SetRange;
property Font: TKMFont read fFont write fFont;
property MinValue: Word read fMinValue;
property MaxValue: Word read fMaxValue;
property ThumbText: UnicodeString read fThumbText write SetThumbText;
property AutoThumbWidth: Boolean read fAutoThumbWidth write SetAutoThumbWidth;
property FixedThumbWidth: Boolean read fAutoThumbWidth write fFixedThumbWidth;
procedure ResetRange;
property OnChange: TNotifyEvent read fOnChange write fOnChange;
procedure MouseDown(X,Y: Integer; Shift: TShiftState; Button: TMouseButton); override;
procedure MouseMove(X,Y: Integer; Shift: TShiftState); override;
procedure MouseWheel(Sender: TObject; WheelSteps: Integer; var aHandled: Boolean); override;
procedure Paint; override;
const
THUMB_WIDTH_ADD = 24;
end;
implementation
uses
SysUtils, Math,
KromUtils, KromOGLUtils,
KM_RenderUI,
KM_Resource, KM_ResTypes;
{ TKMTrackBar }
constructor TKMTrackBar.Create(aParent: TKMPanel; aLeft, aTop, aWidth: Integer; aMin, aMax: Word);
begin
inherited Create(aParent, aLeft,aTop,aWidth,0);
Assert(aMax > aMin, 'TKMTrackBar''s minValue >= maxValue');
fMinValue := aMin;
fMaxValue := aMax;
fTrackHeight := 20;
fRange := KMRange(aMin, aMax); //set Range before position
Position := (fMinValue + fMaxValue) div 2;
Caption := '';
Font := fntMetal;
SliderFont := fntMetal;
CaptionWidth := -1;
Step := 1;
fAutoThumbWidth := False;
fFixedThumbWidth := False;
UpdateThumbWidth;
end;
procedure TKMTrackBar.UpdateThumbWidth;
begin
if fFixedThumbWidth then Exit;
if AutoThumbWidth then
ThumbWidth := Max(gRes.Fonts[SliderFont].GetTextSize(IntToStr(MaxValue)).X,
gRes.Fonts[SliderFont].GetTextSize(ThumbText).X)
else
ThumbWidth := gRes.Fonts[SliderFont].GetTextSize(IntToStr(MaxValue)).X;
ThumbWidth := ThumbWidth + THUMB_WIDTH_ADD;
end;
procedure TKMTrackBar.SetAutoThumbWidth(const aValue: Boolean);
begin
fAutoThumbWidth := aValue;
UpdateThumbWidth;
end;
procedure TKMTrackBar.SetCaption(const aValue: UnicodeString);
begin
fCaption := aValue;
if Trim(fCaption) <> '' then
begin
SetHeightSilently(20 + fTrackHeight);
fTrackTop := 20;
end
else
begin
SetHeightSilently(fTrackHeight);
fTrackTop := 0;
end;
end;
procedure TKMTrackBar.SetPosition(aValue: Word);
begin
fPosition := KMEnsureRange(aValue, Range);
ThumbText := IntToStr(fPosition);
end;
procedure TKMTrackBar.SetRange(const aRange: TKMRangeInt);
begin
fRange.Min := EnsureRange(aRange.Min, MinValue, MaxValue);
fRange.Max := EnsureRange(aRange.Max, MinValue, MaxValue);
Position := fPosition; //Update position due to range change
end;
procedure TKMTrackBar.SetThumbText(const Value: UnicodeString);
begin
fThumbText := Value;
UpdateThumbWidth;
end;
procedure TKMTrackBar.ResetRange;
begin
fRange.Min := MinValue;
fRange.Max := MaxValue;
end;
procedure TKMTrackBar.MouseDown(X,Y: Integer; Shift: TShiftState; Button: TMouseButton);
begin
inherited;
MouseMove(X, Y, Shift);
end;
procedure TKMTrackBar.MouseMove(X,Y: Integer; Shift: TShiftState);
var
newPos: Integer;
posX, stepX: Single;
begin
inherited;
newPos := Position;
if (ssLeft in Shift)
and InRange(Y - AbsTop - fTrackTop, 0, fTrackHeight) then
begin
posX := 0;
if AutoThumbWidth then
begin
stepX := (Width - ThumbWidth) / (fMaxValue - fMinValue) / Step; // width between marks
posX := stepX * (Position / Step) + (ThumbWidth div 2); // actual marks positions on track
end;
if not AutoThumbWidth
or not InRange(X - AbsLeft, posX - ThumbWidth div 2, posX + ThumbWidth div 2) then // do not update pos, if we hover over thumb
newPos := EnsureRange(fMinValue + Round(((X-AbsLeft-ThumbWidth div 2) / (Width - ThumbWidth - 4))*(fMaxValue - fMinValue)/Step)*Step, fMinValue, fMaxValue);
end;
if newPos <> Position then
begin
Position := newPos;
if Assigned(fOnChange) then
fOnChange(Self);
end;
end;
function TKMTrackBar.DoHandleMouseWheelByDefault: Boolean;
begin
Result := False;
end;
procedure TKMTrackBar.MouseWheel(Sender: TObject; WheelSteps: Integer; var aHandled: Boolean);
var
newPos: Integer;
begin
inherited;
if aHandled or (WheelSteps = 0) then Exit;
aHandled := WheelSteps <> 0;
Focus;
newPos := Position;
if WheelSteps <> 0 then
newPos := EnsureRange(newPos - Step*fMouseWheelStep*WheelSteps, fMinValue, fMaxValue);
if newPos <> Position then
begin
Position := newPos;
if Assigned(fOnChange) then
fOnChange(Self);
end;
end;
procedure TKMTrackBar.Paint;
const
TEXT_COLOR: array [Boolean] of TColor4 = ($FF888888, $FFFFFFFF);
var
thumbPos, thumbHeight, rangeMinPos, rangeMaxPos: Word;
capWidth: Integer;
begin
inherited;
if fCaption <> '' then
begin
if CaptionWidth = -1 then
capWidth := Width
else
capWidth := CaptionWidth;
TKMRenderUI.WriteText(AbsLeft, AbsTop, capWidth, fCaption, fFont, taLeft, TEXT_COLOR[Enabled]);
end;
rangeMinPos := Round(Width*(fRange.Min-fMinValue) / (fMaxValue - fMinValue));
rangeMaxPos := Round(Width*(fRange.Max-fMinValue) / (fMaxValue - fMinValue));
TKMRenderUI.WriteBevel(AbsLeft, AbsTop+fTrackTop+1, rangeMinPos, fTrackHeight-2, 0, 0.3);
TKMRenderUI.WriteBevel(AbsLeft + rangeMinPos, AbsTop+fTrackTop+2, rangeMaxPos - rangeMinPos, fTrackHeight-4);
TKMRenderUI.WriteBevel(AbsLeft + rangeMaxPos, AbsTop+fTrackTop+1, Width - rangeMaxPos, fTrackHeight-2, 0, 0.3);
thumbPos := Round(Mix (0, Width - ThumbWidth, 1-(Position-fMinValue) / (fMaxValue - fMinValue)));
thumbHeight := gRes.Sprites[rxGui].RXData.Size[132].Y;
TKMRenderUI.WritePicture(AbsLeft + thumbPos, AbsTop+fTrackTop, ThumbWidth, thumbHeight, [anLeft,anRight], rxGui, 132);
TKMRenderUI.WriteText(AbsLeft + thumbPos + ThumbWidth div 2, AbsTop+fTrackTop+3, 0, ThumbText, SliderFont, taCenter, TEXT_COLOR[Enabled]);
end;
end.
+23
View File
@@ -0,0 +1,23 @@
unit KM_ControlsTypes;
{$I KaM_Remake.inc}
interface
type
TKMAllowedChars = (
acDigits, // Only 0..9 digits, for numeric input
acHex, // Only 0..9 A..F a..f, for input hex values (colors)
acANSI7, // #33..#123,#125,#126 - only basic latin chars and symbols for user nickname, except |
acFileName, // Exclude symbols that can't be used in filenames
acText, // Anything is allowed except for eol symbol
acAll // Anything is allowed
);
const
CTRL_HIGHLIGHT_COEF_DEF = 0.4;
implementation
end.
+62
View File
@@ -0,0 +1,62 @@
unit KM_ControlsUtils;
{$I KaM_Remake.inc}
interface
uses
Classes,
KM_ControlsTypes,
KM_CommonTypes;
// Check if specified aChar is allowed for specified aAllowedChars type
function IsCharAllowed(aChar: WideChar; aAllowedChars: TKMAllowedChars): Boolean;
function IsSelecting(Key: Word; Shift: TShiftState): Boolean;
function GetCursorDir(aKey: Word): TKMCursorDir;
implementation
uses
{$IFDEF MSWindows} Windows, {$ENDIF}
{$IFDEF Unix} LCLIntf, LCLType, {$ENDIF}
Math,
KromUtils;
// Check if specified aChar is allowed for specified aAllowedChars type
function IsCharAllowed(aChar: WideChar; aAllowedChars: TKMAllowedChars): Boolean;
const
HEX_DIGITS: TSetOfAnsiChar = [#48..#57, #65..#70, #97..#102]; // 0..9 A..F a..f
ANSI7_CHARS: TSetOfAnsiChar = [#32..#123, #125..#126]; // except | character
NON_FILE_CHARS: TSetOfAnsiChar = [#0 .. #31, '<', '>', #176, '|', '"', '\', '/', ':', '*', '?'];
NON_TEXT_CHARS_WEOL: TSetOfAnsiChar = [#0 .. #31, #176, '|']; // ° has negative width so acts like a backspace in KaM fonts
NON_TEXT_CHARS: TSetOfAnsiChar = [#0 .. #31, #176]; // ° has negative width so acts like a backspace in KaM fonts
begin
Result := not ((aAllowedChars = acDigits) and not InRange(Ord(aChar), 48, 57)
or (aAllowedChars = acHex) and not CharInSet(aChar, HEX_DIGITS)
or (aAllowedChars = acANSI7) and not CharInSet(aChar, ANSI7_CHARS)
or (aAllowedChars = acFileName) and CharInSet(aChar, NON_FILE_CHARS)
or (aAllowedChars = acText) and CharInSet(aChar, NON_TEXT_CHARS_WEOL)
or (aAllowedChars = acAll) and CharInSet(aChar, NON_TEXT_CHARS));
end;
function IsSelecting(Key: Word; Shift: TShiftState): Boolean;
begin
Result := (ssShift in Shift) and (Key <> VK_SHIFT);
end;
function GetCursorDir(aKey: Word): TKMCursorDir;
begin
case aKey of
VK_LEFT: Result := cdBack;
VK_RIGHT: Result := cdForward;
else
Result := cdNone;
end;
end;
end.
+309
View File
@@ -0,0 +1,309 @@
unit KM_ControlsWaresRow;
{$I KaM_Remake.inc}
interface
uses
Classes, Controls,
KM_Controls, KM_ControlsBase,
KM_ResTypes,
KM_CommonTypes, KM_Defaults;
type
// Row with resource name and icons
TKMWaresRow = class(TKMButtonFlatCommon)
public
WareCount: Word;
WareCntAsNumber: Boolean;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth: Integer); overload;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth: Integer; aClickable: Boolean); overload;
procedure Paint; override;
end;
// Ware order bar
TKMWareOrderRow = class(TKMControl)
private
fWaresRow: TKMWaresRow;
fOrderAdd: TKMButton;
fOrderLab: TKMLabel;
fOrderRem: TKMButton;
fOrderCount: Integer;
fImmediateOrder: Boolean; // Order count can be changed immediately in control. Always set False because of MP delay
procedure ButtonClick(Sender: TObject; Shift: TShiftState);
procedure ClickHold(Sender: TObject; Button: TMouseButton; var aHandled: Boolean);
procedure SetOrderRemHint(const aValue: UnicodeString);
procedure SetOrderAddHint(const aValue: UnicodeString);
procedure SetOrderCount(aValue: Integer);
protected
procedure SetTop(aValue: Integer); override;
procedure SetEnabled(aValue: Boolean); override;
procedure SetVisible(aValue: Boolean); override;
function DoHandleMouseWheelByDefault: Boolean; override;
public
OrderCntMax: Integer;
OnChange: TKMNotifyEventInteger;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth: Integer; aOrderCntMax: Integer = MAX_WARES_IN_HOUSE);
property WareRow: TKMWaresRow read fWaresRow;
property OrderCount: Integer read fOrderCount write SetOrderCount;
property OrderRemHint: UnicodeString write SetOrderRemHint;
property OrderAddHint: UnicodeString write SetOrderAddHint;
procedure MouseWheel(Sender: TObject; WheelSteps: Integer; var aHandled: Boolean); override;
procedure Paint; override;
end;
// Production cost bar
TKMCostsRow = class(TKMControl)
public
RX: TRXType;
TexID1, TexID2: Word;
Count: Byte;
Caption: UnicodeString;
MaxCount: Byte;
constructor Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aMaxCount: Byte = 6);
procedure Paint; override;
end;
implementation
uses
{$IFDEF MSWindows} Windows, {$ENDIF}
{$IFDEF Unix} LCLIntf, LCLType, {$ENDIF}
SysUtils, Math,
KM_RenderUI,
KM_ResFonts,
KM_UtilsExt;
const
WARE_ROW_HEIGHT = 21;
{ TKMWaresRow }
constructor TKMWaresRow.Create(aParent: TKMPanel; aLeft, aTop, aWidth: Integer);
begin
Create(aParent, aLeft, aTop, aWidth, False);
end;
constructor TKMWaresRow.Create(aParent: TKMPanel; aLeft, aTop, aWidth: Integer; aClickable: Boolean);
begin
inherited Create(aParent, aLeft, aTop, aWidth, WARE_ROW_HEIGHT, 0);
Clickable := aClickable;
HideHighlight := not aClickable;
end;
procedure TKMWaresRow.Paint;
var
I: Integer;
begin
inherited;
TKMRenderUI.WriteText(AbsLeft + 4, AbsTop + 3, Width-8, Caption, fntGame, taLeft, $FFE0E0E0);
//Render in reverse order so the rightmost resource is on top (otherwise lighting looks wrong)
if WareCntAsNumber then
begin
TKMRenderUI.WriteText(AbsLeft + Width - 18 - 70 + 24, AbsTop + 3, 40, IntToStr(WareCount), fntGame, taRight, $FFE0E0E0);
TKMRenderUI.WritePicture(AbsLeft + Width - 18, AbsTop + 3, 14, 14, [], RX, TexID);
end else
for I := WareCount - 1 downto 0 do
TKMRenderUI.WritePicture(AbsLeft + Width - 18 - I * 14, AbsTop + 3, 14, 14, [], RX, TexID);
end;
{ TKMWareOrderRow }
constructor TKMWareOrderRow.Create(aParent: TKMPanel; aLeft, aTop, aWidth: Integer; aOrderCntMax: Integer = MAX_WARES_IN_HOUSE);
begin
inherited Create(aParent, aLeft, aTop, aWidth, WARE_ROW_HEIGHT);
fWaresRow := TKMWaresRow.Create(aParent, aLeft + 68, aTop, aWidth - 68);
fImmediateOrder := False;
OrderCntMax := aOrderCntMax;
fOrderRem := TKMButton.Create(aParent, aLeft, 0, 20, Height - 2, '-', bsGame);
fOrderAdd := TKMButton.Create(aParent, aLeft + 46, 0, 20, Height - 2, '+', bsGame);
//Label after buttons, to be sure it will be on top of them, to let player read value from it, if too long (more then 3 symbols)
fOrderLab := TKMLabel.Create (aParent, aLeft + 33, 0, '', fntGrey, taCenter);
fOrderAdd.CapOffsetY := 1;
fOrderRem.OnClickShift := ButtonClick;
fOrderAdd.OnClickShift := ButtonClick;
fOrderRem.OnMouseWheel := MouseWheel;
fOrderAdd.OnMouseWheel := MouseWheel;
fWaresRow.OnMouseWheel := MouseWheel;
fOrderRem.OnClickHold := ClickHold;
fOrderAdd.OnClickHold := ClickHold;
end;
procedure TKMWareOrderRow.ButtonClick(Sender: TObject; Shift: TShiftState);
var
amt: Integer;
begin
amt := GetMultiplicator(Shift);
if Sender = fOrderRem then
amt := -amt;
if amt = 0 then Exit;
if fImmediateOrder then
OrderCount := fOrderCount + amt;
Focus;
if Assigned(OnChange) then
OnChange(Self, amt);
end;
function TKMWareOrderRow.DoHandleMouseWheelByDefault: Boolean;
begin
Result := False;
end;
procedure TKMWareOrderRow.MouseWheel(Sender: TObject; WheelSteps: Integer; var aHandled: Boolean);
var
amt: Integer;
begin
inherited;
if aHandled then Exit;
amt := MouseWheelStep * WheelSteps;
if GetKeyState(VK_SHIFT) < 0 then
amt := amt * 10;
if fImmediateOrder then
OrderCount := fOrderCount + amt;
aHandled := amt <> 0;
Focus;
if Assigned(OnChange) then
OnChange(Self, amt);
end;
procedure TKMWareOrderRow.ClickHold(Sender: TObject; Button: TMouseButton; var aHandled: Boolean);
var
amt: Integer;
begin
inherited;
aHandled := True;
amt := GetMultiplicator(Button);
if Sender = fOrderRem then
amt := -amt;
if amt = 0 then Exit;
if fImmediateOrder then
OrderCount := fOrderCount + amt;
if Assigned(OnChange) then
OnChange(Self, amt);
end;
procedure TKMWareOrderRow.SetOrderCount(aValue: Integer);
begin
fOrderCount := EnsureRange(aValue, 0, OrderCntMax);
end;
procedure TKMWareOrderRow.SetOrderRemHint(const aValue: UnicodeString);
begin
fOrderRem.Hint := aValue;
end;
procedure TKMWareOrderRow.SetOrderAddHint(const aValue: UnicodeString);
begin
fOrderAdd.Hint := aValue;
end;
procedure TKMWareOrderRow.SetTop(aValue: Integer);
begin
inherited;
fWaresRow.Top := aValue;
end;
//Copy property to buttons
procedure TKMWareOrderRow.SetEnabled(aValue: Boolean);
begin
inherited;
fWaresRow.Enabled := Enabled;
fOrderRem.Enabled := Enabled;
fOrderLab.Enabled := Enabled;
fOrderAdd.Enabled := Enabled;
end;
//Copy property to buttons. Otherwise they won't be rendered
procedure TKMWareOrderRow.SetVisible(aValue: Boolean);
begin
inherited;
fWaresRow.Visible := IsSetVisible;
fOrderRem.Visible := IsSetVisible;
fOrderLab.Visible := IsSetVisible;
fOrderAdd.Visible := IsSetVisible;
end;
procedure TKMWareOrderRow.Paint;
begin
inherited;
fOrderRem.Top := Top + 1; //Use internal fTop instead of GetTop (which will return absolute value)
fOrderLab.Top := Top + 4;
fOrderAdd.Top := Top + 1;
fOrderLab.Caption := IntToKStr(OrderCount, 1000);
end;
{ TKMCostsRow }
constructor TKMCostsRow.Create(aParent: TKMPanel; aLeft, aTop, aWidth, aHeight: Integer; aMaxCount: Byte = 6);
begin
inherited Create(aParent, aLeft, aTop, aWidth, aHeight);
MaxCount := aMaxCount;
end;
procedure TKMCostsRow.Paint;
var
I, gap: Integer;
begin
inherited;
TKMRenderUI.WriteText(AbsLeft, AbsTop + 4, Width-20, Caption, fntGrey, taLeft, $FFFFFFFF);
if Count > 0 then
begin
if Count <= MaxCount then
gap := 20
else
gap := Trunc(MaxCount * 20 / Count);
if TexID1 <> 0 then
for I := Count - 1 downto 0 do
TKMRenderUI.WritePicture(AbsLeft+Width-gap*(I+1), AbsTop, 20, Height, [], RX, TexID1);
end else
begin
if TexID1 <> 0 then
TKMRenderUI.WritePicture(AbsLeft+Width-40, AbsTop, 20, Height, [], RX, TexID1);
if TexID2 <> 0 then
TKMRenderUI.WritePicture(AbsLeft+Width-20, AbsTop, 20, Height, [], RX, TexID2);
end;
end;
end.