diff --git a/components/customdrawn/customdrawn_android.pas b/components/customdrawn/customdrawn_android.pas index 285ca8d883..4560f17049 100644 --- a/components/customdrawn/customdrawn_android.pas +++ b/components/customdrawn/customdrawn_android.pas @@ -6,7 +6,7 @@ interface uses // RTL - Classes, SysUtils, Types, + Classes, SysUtils, Types, Math, // fpimage fpcanvas, fpimgcanv, fpimage, // LCL -> Use only TForm, TWinControl, TCanvas and TLazIntfImage @@ -21,6 +21,14 @@ type TCDDrawerAndroid = class(TCDDrawerCommon) private bmpCheckbox, bmpCheckboxChecked: TBitmap; + // Draws a line alternating between two colors + procedure DrawAndroidAlternatedHorzLine(ADest: TCanvas; X1, X2, + Y: Integer; AColor1, AColor2: TColor); + // Draws 2 mixed gradients which alternate between one another on each pixel + procedure DrawAndroidMixedVertGradientFill(ADest: TCanvas; ARect: TRect; + AStart1, AStop1, AStart2, AStop2: TColor); + // Fills a rectangular area alternating between two pixels + procedure DrawAndroidMixedFill(ADest: TCanvas; ARect: TRect; AColor1, AColor2: TColor); public procedure CreateResources; override; procedure LoadResources; override; @@ -49,9 +57,9 @@ type // Standard Tab // =================================== // TCDButton -{ procedure DrawButton(ADest: TCanvas; ADestPos: TPoint; ASize: TSize; - AState: TCDControlState; AStateEx: TCDControlStateEx); override; - // TCDEdit + procedure DrawButton(ADest: TCanvas; ASize: TSize; + AState: TCDControlState; AStateEx: TCDButtonStateEx); override; +{ // TCDEdit procedure DrawEditBackground(ADest: TCanvas; ADestPos: TPoint; ASize: TSize; AState: TCDControlState; AStateEx: TCDEditStateEx); override;} // TCDCheckBox @@ -64,6 +72,54 @@ implementation const ANDROID_DPI = 'vldpi'; + // Actually Android buttons are much more complex then this, + // but this approximation works well + ANDROID_BUTTON_CORNERS = $006B696B; + + ANDROID_BUTTON_FIRST_LINE_A = $00F7F3F7; + ANDROID_BUTTON_FIRST_LINE_B = $00EFF3EF; + ANDROID_BUTTON_SECOND_LINE_A = $00EFF3EF; + ANDROID_BUTTON_SECOND_LINE_B = $00F7F3F7; + ANDROID_BUTTON_THIRD_LINE_A = $00EFEFEF; + ANDROID_BUTTON_THIRD_LINE_B = $00F7F3F7; + + ANDROID_BUTTON_TOP_GRADIENT_A = $00EFEFEF; + ANDROID_BUTTON_TOP_GRADIENT_B = $00EFEBEF; + ANDROID_BUTTON_MIDDLE_GRADIENT_A = $00CBCECB; + ANDROID_BUTTON_MIDDLE_GRADIENT_B = $00CECFCE; + ANDROID_BUTTON_BOTTOM_GRADIENT_A = $00BDBABD; + ANDROID_BUTTON_BOTTOM_GRADIENT_B = $00BABDBA; + + ANDROID_BUTTON_PREPRELAST_LINE_A = $00C6C3C6; + ANDROID_BUTTON_PREPRELAST_LINE_B = $00C6C3C6; + ANDROID_BUTTON_PRELAST_LINE_A = $00C6CBC6; + ANDROID_BUTTON_PRELAST_LINE_B = $00CECBCE; + ANDROID_BUTTON_LAST_LINE_A = $00D6D3D6; + ANDROID_BUTTON_LAST_LINE_B = $00D6D7D6; + + // Sunken variants + + ANDROID_BUTTON_SUNKEN_FIRST_LINE_A = $0066F366; + ANDROID_BUTTON_SUNKEN_FIRST_LINE_B = $0066F366; + ANDROID_BUTTON_SUNKEN_SECOND_LINE_A = $0066F366; + ANDROID_BUTTON_SUNKEN_SECOND_LINE_B = $0066F366; + ANDROID_BUTTON_SUNKEN_THIRD_LINE_A = $0066EF66; + ANDROID_BUTTON_SUNKEN_THIRD_LINE_B = $0066F366; + + ANDROID_BUTTON_SUNKEN_TOP_GRADIENT_A = $0066EF66; + ANDROID_BUTTON_SUNKEN_TOP_GRADIENT_B = $0066EB66; + ANDROID_BUTTON_SUNKEN_MIDDLE_GRADIENT_A = $0033CE33; + ANDROID_BUTTON_SUNKEN_MIDDLE_GRADIENT_B = $0033CF33; + ANDROID_BUTTON_SUNKEN_BOTTOM_GRADIENT_A = $0000BA00; + ANDROID_BUTTON_SUNKEN_BOTTOM_GRADIENT_B = $0000BD00; + + ANDROID_BUTTON_SUNKEN_PREPRELAST_LINE_A = $0000C300; + ANDROID_BUTTON_SUNKEN_PREPRELAST_LINE_B = $0000C300; + ANDROID_BUTTON_SUNKEN_PRELAST_LINE_A = $0000CB00; + ANDROID_BUTTON_SUNKEN_PRELAST_LINE_B = $0000CB00; + ANDROID_BUTTON_SUNKEN_LAST_LINE_A = $0000D300; + ANDROID_BUTTON_SUNKEN_LAST_LINE_B = $0000D700; + {procedure TCDButtonDrawerAndroid.DrawToIntfImage(ADest: TFPImageCanvas; CDButton: TCDButton); begin @@ -111,6 +167,83 @@ initialization { TCDDrawerAndroid } +procedure TCDDrawerAndroid.DrawAndroidAlternatedHorzLine(ADest: TCanvas; + X1, X2, Y: Integer; AColor1, AColor2: TColor); +var + i: Integer; +begin + for i := X1 to X2-1 do + begin + if i mod 2 = 0 then ADest.Pixels[i, Y] := AColor1 + else ADest.Pixels[i, Y] := AColor2; + end; +end; + +procedure TCDDrawerAndroid.DrawAndroidMixedVertGradientFill(ADest: TCanvas; + ARect: TRect; AStart1, AStop1, AStart2, AStop2: TColor); +var + RStart1, RStop1, RStart2, RStop2: Byte; + GStart1, GStop1, GStart2, GStop2: Byte; + BStart1, BStop1, BStart2, BStop2: Byte; + RDiff1, GDiff1, BDiff1: Integer; + RDiff2, GDiff2, BDiff2: Integer; + Count, I: Integer; + lColor1, lColor2: TColor; +begin + if IsRectEmpty(ARect) then Exit; + + RedGreenBlue(ColorToRGB(AStart1), RStart1, GStart1, BStart1); + RedGreenBlue(ColorToRGB(AStop1), RStop1, GStop1, BStop1); + RedGreenBlue(ColorToRGB(AStart2), RStart2, GStart2, BStart2); + RedGreenBlue(ColorToRGB(AStop2), RStop2, GStop2, BStop2); + + RDiff1 := RStop1 - RStart1; + GDiff1 := GStop1 - GStart1; + BDiff1 := BStop1 - BStart1; + + RDiff2 := RStop2 - RStart2; + GDiff2 := GStop2 - GStart2; + BDiff2 := BStop2 - BStart2; + + Count := ARect.Bottom - ARect.Top; + + for I := 0 to Count-1 do + begin + lColor1 := RGBToColor(RStart1 + (i * RDiff1) div Count, + GStart1 + (i * GDiff1) div Count, + BStart1 + (i * BDiff1) div Count); + lColor2 := RGBToColor(RStart2 + (i * RDiff2) div Count, + GStart2 + (i * GDiff2) div Count, + BStart2 + (i * BDiff2) div Count); + + // draw left to right, because LineTo does not draw last pixel + DrawAndroidAlternatedHorzLine(ADest, ARect.Left, ARect.Right, ARect.Top+I, lColor1, lColor2); + end; +end; + +procedure TCDDrawerAndroid.DrawAndroidMixedFill(ADest: TCanvas; ARect: TRect; + AColor1, AColor2: TColor); +var + I: Integer; + lColor1, lColor2: TColor; +begin + for I := 0 to (ARect.Bottom - ARect.Top)-1 do + begin + if i mod 2 = 0 then + begin + lColor1 := AColor1; + lColor2 := AColor2; + end + else + begin + lColor1 := AColor2; + lColor2 := AColor1; + end; + // draw left to right, because LineTo does not draw last pixel + DrawAndroidAlternatedHorzLine(ADest, ARect.Left, ARect.Right, ARect.Top+I, lColor1, lColor2); + end; +end; + procedure TCDDrawerAndroid.CreateResources; begin bmpCheckbox := TBitmap.Create; @@ -177,6 +310,124 @@ begin // Don't draw anything, tickmarks are impressed into the general images end; +procedure TCDDrawerAndroid.DrawButton(ADest: TCanvas; ASize: TSize; + AState: TCDControlState; AStateEx: TCDButtonStateEx); +var + Str: string; + lGlyphLeftSpacing: Integer = 0; + lTextOutPos: TPoint; + lGlyphCaptionHeight: Integer; + lColor: TColor; + lRect: TRect; +begin + // Background corners + lColor := AStateEx.ParentRGBColor; + ADest.Pixels[0, 0] := lColor; + ADest.Pixels[1, 0] := lColor; + ADest.Pixels[0, 1] := lColor; + ADest.Pixels[ASize.cx-1, 0] := lColor; + ADest.Pixels[ASize.cx-2, 0] := lColor; + ADest.Pixels[ASize.cx-1, 1] := lColor; + ADest.Pixels[0, ASize.cy-1] := lColor; + ADest.Pixels[1, ASize.cy-1] := lColor; + ADest.Pixels[0, ASize.cy-2] := lColor; + ADest.Pixels[ASize.cx-1, ASize.cy-1] := lColor; + ADest.Pixels[ASize.cx-2, ASize.cy-1] := lColor; + ADest.Pixels[ASize.cx-1, ASize.cy-2] := lColor; + + // Darker corners + lColor := ANDROID_BUTTON_CORNERS; + ADest.Pixels[1, 1] := lColor; + ADest.Pixels[2, 0] := lColor; + ADest.Pixels[0, 2] := lColor; + ADest.Pixels[ASize.cx-3, 0] := lColor; + ADest.Pixels[ASize.cx-2, 1] := lColor; + ADest.Pixels[ASize.cx-1, 2] := lColor; + ADest.Pixels[0, ASize.cy-3] := lColor; + ADest.Pixels[1, ASize.cy-2] := lColor; + ADest.Pixels[2, ASize.cy-1] := lColor; + ADest.Pixels[ASize.cx-1, ASize.cy-3] := lColor; + ADest.Pixels[ASize.cx-2, ASize.cy-2] := lColor; + ADest.Pixels[ASize.cx-3, ASize.cy-1] := lColor; + + // Button image + if csfSunken in AState then + begin + // Top lines + DrawAndroidAlternatedHorzLine(ADest, 3, ASize.cx-3, 0, ANDROID_BUTTON_SUNKEN_FIRST_LINE_A, ANDROID_BUTTON_SUNKEN_FIRST_LINE_B); + DrawAndroidAlternatedHorzLine(ADest, 2, ASize.cx-2, 1, ANDROID_BUTTON_SUNKEN_SECOND_LINE_A, ANDROID_BUTTON_SUNKEN_SECOND_LINE_B); + DrawAndroidAlternatedHorzLine(ADest, 1, ASize.cx-1, 2, ANDROID_BUTTON_SUNKEN_THIRD_LINE_A, ANDROID_BUTTON_SUNKEN_THIRD_LINE_B); + + // The central gradient + lRect := Bounds(0, 3, ASize.cx, (ASize.cy-6) div 3); + DrawAndroidMixedVertGradientFill(ADest, lRect, ANDROID_BUTTON_SUNKEN_TOP_GRADIENT_A, + ANDROID_BUTTON_SUNKEN_MIDDLE_GRADIENT_A, ANDROID_BUTTON_SUNKEN_TOP_GRADIENT_B, ANDROID_BUTTON_SUNKEN_MIDDLE_GRADIENT_B); + lRect := Bounds(0, 3+(ASize.cy-6) div 3, ASize.cx, (ASize.cy-6) div 3); + DrawAndroidMixedFill(ADest, lRect, ANDROID_BUTTON_SUNKEN_MIDDLE_GRADIENT_A, ANDROID_BUTTON_SUNKEN_MIDDLE_GRADIENT_B); + lRect := Bounds(0, 3+2*(ASize.cy-6) div 3, ASize.cx, (ASize.cy-6) div 3); + DrawAndroidMixedVertGradientFill(ADest, lRect, ANDROID_BUTTON_SUNKEN_MIDDLE_GRADIENT_A, + ANDROID_BUTTON_SUNKEN_BOTTOM_GRADIENT_A, ANDROID_BUTTON_SUNKEN_MIDDLE_GRADIENT_B, ANDROID_BUTTON_SUNKEN_BOTTOM_GRADIENT_B); + + // Bottom lines + DrawAndroidAlternatedHorzLine(ADest, 1, ASize.cx-1, ASize.cy-3, ANDROID_BUTTON_SUNKEN_PREPRELAST_LINE_A, ANDROID_BUTTON_SUNKEN_PREPRELAST_LINE_B); + DrawAndroidAlternatedHorzLine(ADest, 2, ASize.cx-2, ASize.cy-2, ANDROID_BUTTON_SUNKEN_PRELAST_LINE_A, ANDROID_BUTTON_SUNKEN_PRELAST_LINE_B); + DrawAndroidAlternatedHorzLine(ADest, 3, ASize.cx-3, ASize.cy-1, ANDROID_BUTTON_SUNKEN_LAST_LINE_A, ANDROID_BUTTON_SUNKEN_LAST_LINE_B); + end + else + begin + // Top lines + DrawAndroidAlternatedHorzLine(ADest, 3, ASize.cx-3, 0, ANDROID_BUTTON_FIRST_LINE_A, ANDROID_BUTTON_FIRST_LINE_B); + DrawAndroidAlternatedHorzLine(ADest, 2, ASize.cx-2, 1, ANDROID_BUTTON_SECOND_LINE_A, ANDROID_BUTTON_SECOND_LINE_B); + DrawAndroidAlternatedHorzLine(ADest, 1, ASize.cx-1, 2, ANDROID_BUTTON_THIRD_LINE_A, ANDROID_BUTTON_THIRD_LINE_B); + + // The central gradient + lRect := Bounds(0, 3, ASize.cx, (ASize.cy-6) div 3); + DrawAndroidMixedVertGradientFill(ADest, lRect, ANDROID_BUTTON_TOP_GRADIENT_A, + ANDROID_BUTTON_MIDDLE_GRADIENT_A, ANDROID_BUTTON_TOP_GRADIENT_B, ANDROID_BUTTON_MIDDLE_GRADIENT_B); + lRect := Bounds(0, 3+(ASize.cy-6) div 3, ASize.cx, (ASize.cy-6) div 3); + DrawAndroidMixedFill(ADest, lRect, ANDROID_BUTTON_MIDDLE_GRADIENT_A, ANDROID_BUTTON_MIDDLE_GRADIENT_B); + lRect := Bounds(0, 3+2*(ASize.cy-6) div 3, ASize.cx, (ASize.cy-6) div 3); + DrawAndroidMixedVertGradientFill(ADest, lRect, ANDROID_BUTTON_MIDDLE_GRADIENT_A, + ANDROID_BUTTON_BOTTOM_GRADIENT_A, ANDROID_BUTTON_MIDDLE_GRADIENT_B, ANDROID_BUTTON_BOTTOM_GRADIENT_B); + + // Bottom lines + DrawAndroidAlternatedHorzLine(ADest, 1, ASize.cx-1, ASize.cy-3, ANDROID_BUTTON_PREPRELAST_LINE_A, ANDROID_BUTTON_PREPRELAST_LINE_B); + DrawAndroidAlternatedHorzLine(ADest, 2, ASize.cx-2, ASize.cy-2, ANDROID_BUTTON_PRELAST_LINE_A, ANDROID_BUTTON_PRELAST_LINE_B); + DrawAndroidAlternatedHorzLine(ADest, 3, ASize.cx-3, ASize.cy-1, ANDROID_BUTTON_LAST_LINE_A, ANDROID_BUTTON_LAST_LINE_B); + end; + + if csfHasFocus in AState then + DrawFocusRect(ADest, Point(5, 5), Size(ASize.cx-10, ASize.cy-10)); + + // Position calculations + ADest.Font.Assign(AStateEx.Font); + Str := AStateEx.Caption; + lGlyphCaptionHeight := Max(ADest.TextHeight(Str), AStateEx.Glyph.Height); + lTextOutPos.X := (ASize.cx - ADest.TextWidth(Str) - AStateEx.Glyph.Width) div 2; + lTextOutPos.Y := (ASize.cy - lGlyphCaptionHeight) div 2; + lTextOutPos.X := Max(lTextOutPos.X, 5); + lTextOutPos.Y := Max(lTextOutPos.Y, 5); + + // Button glyph + if not AStateEx.Glyph.Empty then + begin + ADest.Draw(lTextOutPos.X, lTextOutPos.Y, AStateEx.Glyph); + lGlyphLeftSpacing := AStateEx.Glyph.Width+5; + end; + + // Button text + lTextOutPos.X := lTextOutPos.X + lGlyphLeftSpacing; + lTextOutPos.Y := (ASize.cy - ADest.TextHeight(Str)) div 2; + ADest.Brush.Style := bsClear; + ADest.Pen.Style := psSolid; + if csfSunken in AState then + begin + Inc(lTextOutPos.X); + Inc(lTextOutPos.Y); + end; + ADest.TextOut(lTextOutPos.X, lTextOutPos.Y, Str) +end; + procedure TCDDrawerAndroid.DrawCheckBoxSquare(ADest: TCanvas; ADestPos: TPoint; ASize: TSize; AState: TCDControlState; AStateEx: TCDControlStateEx); begin