diff --git a/examples/dockmanager/easytree/easydocking.lpi b/examples/dockmanager/easytree/easydocking.lpi index 2d4aa55cec..b8ef8f6536 100644 --- a/examples/dockmanager/easytree/easydocking.lpi +++ b/examples/dockmanager/easytree/easydocking.lpi @@ -5,6 +5,7 @@ + diff --git a/examples/dockmanager/easytree/easydocksite.pas b/examples/dockmanager/easytree/easydocksite.pas index 8f5ef0c070..a7288a7746 100644 --- a/examples/dockmanager/easytree/easydocksite.pas +++ b/examples/dockmanager/easytree/easydocksite.pas @@ -5,7 +5,16 @@ This project can be used instead of the LDockTree manager. To be added or ported: - field and method argument names - - persistence + +Possible extensions: + - separate docking management and dock site layout + - various dock headers + - multiple splitters (on zones without controls) + - persistence (requires application wide management of dock sources!) + - purpose of Restore button? + +Known bugs: + - Problem with dragging header, seems to interfere with dragmanager (capture)? More issues, concerning the rest of the LCL (mainly unit Controls): @@ -18,11 +27,19 @@ LCL TODO: becomes a DockSite. LCL does not handle docking managers as interfaces. + (this will most probably never come) - LCL undocks a control when it just is clicked. + LCL undocks a control when it just is clicked. (Delphi flaw) LCL controls don't notify the dock manager about visibility changes. + LCL doesn't handle properly the possible start of a drag operation (WM_LBUTTONDOWN). + In DragMode=dmAutomatic the drag manager should capture mouse input. + When an LB_UP occurs while waiting for the threshold, a normal Click should + occur (perform LB_DOWN and LB_UP). + Otherwise dragging starts, and the control has to be reset into "no button down" + state. + done? (unclear whether this is really fixed in the trunk) ===== LCL does not notify the docking manager of a resized dock site. @@ -31,8 +48,10 @@ done? (unclear whether this is really fixed in the trunk) {$H+} -{$DEFINE splitter_color} //use colored splitter, for debugging? +{.$DEFINE splitter_color} //use colored splitter, for debugging? {.$DEFINE NoDrop} //patched dragobject? +{.$DEFINE visibility} //handling of invisible clients deserves dock manager notification! +{.$DEFINE restore} //restore button? interface @@ -75,7 +94,7 @@ type PartRect: TRect; public constructor Create; - class function GetRectOfPart(AHeaderRect: TRect; AOrientation: TDockOrientation; APart: TEasyZonePart; HasSplitter: boolean): TRect; virtual; + class function GetRectOfPart(ARect: TRect; AOrientation: TDockOrientation; APart: TEasyZonePart; HasSplitter: boolean): TRect; virtual; function FindPart(AZone: TEasyZone; MousePos: TPoint; fButtonDown: boolean): TEasyZonePart; procedure Draw(AZone: TEasyZone; ACanvas: TCanvas; ACaption: string; const MousePos: TPoint); end; @@ -336,10 +355,14 @@ end; procedure TEasyTree.AdjustDockRect(Control: TControl; var ARect: TRect); begin //get the client area within the given zone rectangle +{$IFDEF old} if Control.DockOrientation = doVertical then inc(ARect.Top, DockHeaderSize) else inc(ARect.Left, DockHeaderSize); +{$ELSE} + ARect := FHeader.GetRectOfPart(ARect, Control.DockOrientation, zpClient, true); +{$ENDIF} end; function TEasyTree.FindControlZone(zone: TEasyZone; Control: TControl): TEasyZone; @@ -700,17 +723,25 @@ begin {$ENDIF} zpCloseButton: begin + {$IFDEF visibility} + //handling of invisible clients deserves dock manager notification! if Control is TCustomForm then TCustomForm(Control).Close else // not a form => doesnot have close => just hide Control.Visible := False; DockSite.Invalidate; + {$ELSE} + Control.ManualDock(nil, nil, alNone); //do float + {$ENDIF} end; end; LM_LBUTTONDOWN: case FindZone(False) of zpCaption: // mouse down on not buttons => start drag - Control.BeginDrag(False); + begin //problem here - app hangs! + DebugLn('start dragging from header: %s', [FDockSite.GetDockCaption(Control)]); + Control.BeginDrag(False); + end; end; LM_MOUSEMOVE: begin //what's Message.Keys??? @@ -1290,10 +1321,10 @@ begin *) BR := TLBR.BottomRight; if ChildControl <> nil then begin //is control zone - FTree.AdjustDockRect(ChildControl, TLBR); + //FTree.AdjustDockRect(ChildControl, TLBR); + TLBR := FTree.FHeader.GetRectOfPart(TLBR, ChildControl.DockOrientation, zpClient, HasSizer); ChildControl.BoundsRect := TLBR; end else if FirstChild <> nil then begin - //if Orientation = doVertical then TLBR.; z := FirstChild; while z <> nil do begin //resize - for splitter move only! diff --git a/examples/dockmanager/easytree/zoneheader.inc b/examples/dockmanager/easytree/zoneheader.inc index a053057ea2..7852390a47 100644 --- a/examples/dockmanager/easytree/zoneheader.inc +++ b/examples/dockmanager/easytree/zoneheader.inc @@ -37,106 +37,149 @@ end; { TEasyDockHeader } -const +type + TZonePartMap = record + dTop, dBottom, dLeft, dRight: integer; + end; + +const //zone decoration sizes dSizer = 4; dBorder = 2; //frame and inner bevel dDist = 1; //button distance -{$IFDEF newsplitter} - dHeader = 22 - dSizer; //splitter outside header! + dButton = 14; //include some space for themes + dHeader = dButton + 2*dBorder; // 22 - dSizer; //splitter outside header! + +(* Zone part map. + In portrait mode (header on top), the zone rectangle is adjusted according + to the given offsets. In landscape mode (header on the left), the offsets + have to be applied to the rotated coordinates. + + Positive offsets mean self-relative adjustment, towards the opposite edge. + This operation has highest precedence. + Negative offsets mean adjustment relative to the adjusted opposite edge. + + zpAll excludes the splitter and client area. + zpCaption excludes borders and buttons from zpAll. +*) +{$IFDEF newSplitter} +(* + The map reflects new splitter placement (past client area), + and no restore button. +*) +HeaderPartMap: array[TEasyZonePart] of TZonePartMap = ( + (), //zpNowhere + (dTop:dHeader; dBottom:0), //zpClient + (dTop:0; dBottom:-dHeader), //zpAll + (dTop:dBorder; dBottom:-dButton; dLeft:dBorder; dRight:dBorder+dButton), //zpCaption + (dTop:-dSizer), //zpSizer + {$IFDEF restore} + (...), //zpRestoreButton + {$ENDIF} + (dTop:dBorder; dBottom:-dButton; dLeft:-dButton; dRight:dBorder) //zpCloseButton +); {$ELSE} - dHeader = 22; +(* Sizer in header, just before caption +*) +HeaderPartMap: array[TEasyZonePart] of TZonePartMap = ( + (), //zpNowhere - not in any zone + (dTop:dHeader+dSizer; dBottom:0), //zpClient - on client control + (dTop:dSizer; dBottom:-dHeader), //zpAll - total header rect + (dTop:dSizer+dBorder; dBottom:-dButton; dLeft:dBorder; dRight:dBorder+dButton), //zpCaption + (dTop:0; dBottom:-dSizer), //zpSizer - splitter/sizer + {$IFDEF restore} + (dTop:dSizer+dBorder; dBottom:-dButton; dLeft:-dButton; dRight:dButton+2*dBorder) //zpRestoreButton, // header restore button + {$ENDIF} + (dTop:dSizer+dBorder; dBottom:-dButton; dLeft:-dButton; dRight:dBorder) //zpCloseButton // header close button +); {$ENDIF} constructor TEasyDockHeader.Create; -{ + procedure dump; var r, r2: TRect; + const + hc = true; + orn = doVertical; begin - r := Rect(0, 0, 200, HeaderSize); //LTBR - r2 := GetRectOfPart(r, doVertical, zpCaption); + r := Rect(0, 0, 200, 200); //LTBR + r2 := GetRectOfPart(r, orn, zpAll, hc); + DebugLn('%s (%d,%d)-(%d,%d)', ['header ', r2.Top, r2.Left, r2.Bottom, r2.Right]); + r2 := GetRectOfPart(r, orn, zpCaption, hc); DebugLn('%s (%d,%d)-(%d,%d)', ['caption', r2.Top, r2.Left, r2.Bottom, r2.Right]); - r2 := GetRectOfPart(r, doVertical, zpCloseButton); + r2 := GetRectOfPart(r, orn, zpCloseButton, hc); DebugLn('%s (%d,%d)-(%d,%d)', ['closer ', r2.Top, r2.Left, r2.Bottom, r2.Right]); - r2 := GetRectOfPart(r, doVertical, zpSizer); + r2 := GetRectOfPart(r, orn, zpClient, hc); + DebugLn('%s (%d,%d)-(%d,%d)', ['client ', r2.Top, r2.Left, r2.Bottom, r2.Right]); + r2 := GetRectOfPart(r, orn, zpSizer, hc); DebugLn('%s (%d,%d)-(%d,%d)', ['sizer ', r2.Top, r2.Left, r2.Bottom, r2.Right]); end; -} + begin HeaderSize := dHeader; //some meaningful value? //debug //dump; end; -class function TEasyDockHeader.GetRectOfPart(AHeaderRect: TRect; AOrientation: TDockOrientation; +class function TEasyDockHeader.GetRectOfPart(ARect: TRect; AOrientation: TDockOrientation; APart: TEasyZonePart; HasSplitter: boolean): TRect; -var - d, dRight, dWidth: Integer; begin - if (APart = zpNowhere) or (APart = zpClient) - or ((APart = zpSizer) and not HasSplitter) then begin +(* ARect is (must be) TLBR zone rectangle, on input. +*) + if (APart = zpNowhere) or ((APart = zpSizer) and not HasSplitter) then begin Result := Rect(0,0,0,0); exit; end; - Result := AHeaderRect; - //if APart = zpAll then Exit; //include sizer? + Result := ARect; + with HeaderPartMap[APart] do begin + if AOrientation = doVertical then begin //portrait + if dTop > 0 then + inc(Result.Top, dTop); + if dBottom > 0 then + dec(Result.Bottom, dBottom) + else if dBottom < 0 then + Result.Bottom := Result.Top - dBottom; + if dTop < 0 then + Result.Top := Result.Bottom + dTop; + if dLeft > 0 then + inc(Result.Left, dLeft); + if dRight > 0 then + dec(Result.Right, dRight) + else if dRight < 0 then + Result.Right := Result.Left + dRight; + if dLeft < 0 then + Result.Left := Result.Right + dLeft; + {$IFDEF newSplitter} + //handle client w/o splitter + if (APart = zpClient) and HasSplitter then + dec(Result.Bottom, dSizer); + {$ELSE} + {$ENDIF} + end else begin //landscape + if dTop > 0 then + inc(Result.Left, dTop); + if dBottom > 0 then + dec(Result.Right, dBottom) + else if dBottom < 0 then + Result.Right := Result.Left - dBottom; + if dTop < 0 then + Result.Left := Result.Right + dTop; - if APart = zpSizer then begin - //at top/left - visible only if HasSplitter - if AOrientation = doVertical then - Result.Bottom := Result.Top + dSizer - else - Result.Right := Result.Left + dSizer; - exit; - end; -//exclude sizer -if not HasSplitter then begin - if AOrientation = doVertical then - inc(Result.Top, dSizer) - else - inc(Result.Left, dSizer); -end; - - if APart = zpAll then - Exit; //exclude sizer - -//exclude border, assume 1 pixel border, 1 pixel inner distance - InflateRect(Result, -dBorder, -dBorder); //border(2), remaining = rectangular button width/height -//get remaining size for buttons - if AOrientation = doVertical then - d := Result.Bottom - Result.Top - else - d := Result.Right - Result.Left; - - dWidth := 0; - case APart of - //zpAll: - see above - zpCloseButton: dRight := dDist; -{$IFDEF restore} - zpRestoreButton: dRight := d + 2*dDist; - zpCaption: dWidth := 2*(d + dDist); //2 * (button + dist) -{$ELSE} - zpCaption: dWidth := (d + dDist); //1 * (button + dist) -{$ENDIF} - //zpSizer: - see above - //zpClient, //here: invalid argument! - //zpNowhere: Result := Rect(0,0,0,0); - end; - - if AOrientation = doVertical then begin - if dWidth > 0 then begin //caption - dec(Result.Right, dBorder+dWidth); - end else begin //buttons - dec(Result.Right, dBorder+dRight); - Result.Left := Result.Right - d; - end; - end else begin - if dWidth > 0 then begin //caption - inc(Result.Top, dBorder+dWidth); - end else begin //buttons - inc(Result.Top, dBorder+dRight); - Result.Bottom := Result.Top + d; + if dLeft > 0 then + dec(Result.Bottom, dLeft); + if dRight > 0 then + inc(Result.Top, dRight) + else if dRight < 0 then + Result.Top := Result.Bottom + dRight; + if dLeft < 0 then + Result.Bottom := Result.Top - dLeft; + {$IFDEF newSplitter} + //handle client w/o splitter + if (APart = zpClient) and HasSplitter then + dec(Result.Right, dSizer); + {$ELSE} + {$ENDIF} end; end; end; @@ -163,36 +206,30 @@ begin *) r := AZone.GetBounds; if (AZone.FChildControl = nil) or not PtInRect(r, MousePos) then + //possibly empty root zone Result := zpNowhere else begin Control := AZone.FChildControl; - if Control.DockOrientation = doVertical then - r.Bottom := Control.Top - else - r.Right := Control.Left; - if not PtInRect(r, MousePos) then - Part := zpClient //if not in header, must be in control - else if MouseInPart(zpSizer) or MouseInPart(zpCloseButton) + if MouseInPart(zpSizer) or MouseInPart(zpCloseButton) {$IFDEF restore} or MouseInPart(zpRestoreButton) {$ENDIF} + or MouseInPart(zpClient) then - //all done + //all done, result stored in Part else - Part := zpCaption; + Part := zpCaption; //include borders end; aHandle:=AZone.GetHandle; -//check old state changed - if (self.MouseZone <> nil) - and ((MouseZone <> AZone) or (MousePart <> Part) or (MouseDown <> fButtonDown)) then begin - //reset state? - if MousePart in HeaderButtons then +//check old state changed - buttons also change state on mouse enter/exit + if (self.MouseZone <> nil) //else Mouse... invalid + and ((MouseZone <> AZone) or (MousePart in HeaderButtons)) then InvalidateRect(aHandle, @PartRect, false); //old button - end; //check new state - if (MouseDown <> fButtonDown) and (MousePart in HeaderButtons) then - InvalidateRect(aHandle, @SubRect, false); //new button + if (Part in HeaderButtons) then begin + InvalidateRect(aHandle, @SubRect, false); //refresh button + end; //set new state MouseZone := AZone; MousePart := Part; @@ -205,12 +242,15 @@ end; procedure TEasyDockHeader.Draw(AZone: TEasyZone; ACanvas: TCanvas; ACaption: string; const MousePos: TPoint); (* Problem with colors on other than win32 widgetsets (gtk2...) *) -{$DEFINE LDock} //mimic LDockTree? const clBack = clHighlight; clFont = clHighlightText; - procedure DrawButton(ARect: TRect; IsMouseDown, IsMouseOver: Boolean; ABitmap: TCustomBitmap); inline; +var + IsMouseDown: Boolean; + + //procedure DrawButton(ARect: TRect; IsMouseDown, IsMouseOver: Boolean; ABitmap: TCustomBitmap); inline; + procedure DrawButton(ARect: TRect; ABitmap: TCustomBitmap); const // ------------- Pressed, Hot ----------------------- BtnDetail: array[Boolean, Boolean] of TThemedToolBar = @@ -221,18 +261,17 @@ const var Details: TThemedElementDetails; dx, dy: integer; + IsMouseOver: boolean; begin + ACanvas.FillRect(ARect); + IsMouseOver := PtInRect(ARect, MousePos); Details := ThemeServices.GetElementDetails(BtnDetail[IsMouseDown, IsMouseOver]); ThemeServices.DrawElement(ACanvas.Handle, Details, ARect); ARect := ThemeServices.ContentRect(ACanvas.Handle, Details, ARect); - //zoom button into rect? - {$IFDEF LDock} + //move button into rect, excluding border dx := (ARect.Right - ARect.Left - ABitmap.Width) div 2; dy := (ARect.Bottom - ARect.Top - ABitmap.Height) div 2; ACanvas.Draw(ARect.Left + dx, ARect.Top + dy, ABitmap); - {$ELSE} - ACanvas.Draw(ARect.Left, ARect.Top, ABitmap); - {$ENDIF} end; var @@ -242,74 +281,48 @@ var OldFont, RotatedFont: HFONT; OldMode: Integer; ALogFont: TLogFont; - IsMouseDown: Boolean; //obsolete AOrientation: TDockOrientation; AControl: TControl; begin (* Some colors inavailable on some widgetsets! - (NewSplitter at opposite side of header! - not in this version) *) - //IsMouseDown := self.MouseDown; // not always correct? IsMouseDown := (GetKeyState(VK_LBUTTON) and $80) <> 0; //debug AControl := AZone.FChildControl; AOrientation := AControl.DockOrientation; ARect := AZone.GetBounds; - BtnRect := ARect; - if AZone.FChildControl.DockOrientation = doVertical then begin - ARect.Bottom := ARect.Top + HeaderSize; //entire header area - BtnRect.Top := BtnRect.Bottom - dSizer; //splitter - end else begin - ARect.Right := ARect.Left + HeaderSize; - BtnRect.Left := BtnRect.Right - dSizer; - end; - DrawRect := ARect; ACanvas.Brush.Color := clBtnFace; - if AZone.HasSizer then begin -{$IFDEF newsplitter} -// splitter no more in header! - BtnRect initialized above - ACanvas.FillRect(BtnRect); -{$ELSE} //draw splitter? - BtnRect := GetRectOfPart(ARect, AOrientation, zpSizer, AZone.HasSizer); -{$IFDEF LDock} -{$ELSE} - ACanvas.Brush.Color := clBtnFace; -{$ENDIF} - ACanvas.FillRect(BtnRect); -{$ENDIF} + if AZone.HasSizer then begin + // border? + BtnRect := GetRectOfPart(ARect, AOrientation, zpSizer, AZone.HasSizer); + ACanvas.FillRect(BtnRect); end; -//erase? +//erase - which color(s)? DrawRect := GetRectOfPart(ARect, AOrientation, zpAll, AZone.HasSizer); -{$IFDEF LDock} -{$ELSE} - ACanvas.Brush.Color := clBack; // clActiveCaption; -{$ENDIF} + //ACanvas.Brush.Color := clBack; // clActiveCaption; ACanvas.FillRect(DrawRect); -//what's this? +//border InflateRect(DrawRect, -1, -1); //outer bevel? ACanvas.Brush.Color := clBtnShadow; - ACanvas.FrameRect(DrawRect); -{$IFDEF LDock} - InflateRect(DrawRect, -1, -1); //inner bevel? - //the value is not used any more! -{$ELSE} -{$ENDIF} + ACanvas.FrameRect(DrawRect); //1 pixel border + //InflateRect(DrawRect, -1, -1); //inner bevel? + //the value is not used any more! + ACanvas.Brush.Color := clBtnFace; // draw caption -{$IFDEF LDock} -{$ELSE} - ACanvas.Font.Color := clFont; //clCaptionText; -{$ENDIF} + //ACanvas.Font.Color := clFont; //clCaptionText; DrawRect := GetRectOfPart(ARect, AOrientation, zpCaption, AZone.HasSizer); OldMode := SetBkMode(ACanvas.Handle, TRANSPARENT); +//for some reason the caption is not offset properly? if AOrientation = doVertical then begin + inc(DrawRect.Left, 2); DrawText(ACanvas.Handle, PChar(ACaption), -1, DrawRect, DT_LEFT or DT_SINGLELINE or DT_VCENTER) end else begin OldFont := 0; @@ -321,29 +334,25 @@ begin OldFont := SelectObject(ACanvas.Handle, RotatedFont); end; // from msdn: DrawText doesnot support font with orientation and escapement <> 0 - TextOut(ACanvas.Handle, DrawRect.Left, DrawRect.Bottom, PChar(ACaption), Length(ACaption)); + //TextOut(ACanvas.Handle, DrawRect.Left, DrawRect.Bottom, PChar(ACaption), Length(ACaption)); + TextOut(ACanvas.Handle, DrawRect.Left-2, DrawRect.Bottom-2, PChar(ACaption), Length(ACaption)); if OldFont <> 0 then DeleteObject(SelectObject(ACanvas.Handle, OldFont)); end; SetBkMode(ACanvas.Handle, OldMode); // buttons - which colors to use? -{$IFDEF LDock} -{$ELSE} ACanvas.Brush.Color := clBtnFace; //ACanvas.Pen.Color := clButtonText; -{$ENDIF} // draw close button BtnRect := GetRectOfPart(ARect, AOrientation, zpCloseButton, AZone.HasSizer); - ACanvas.FillRect(BtnRect); - DrawButton(BtnRect, IsMouseDown, PtInRect(BtnRect, MousePos), DockBtnImages[dhiClose]); + DrawButton(BtnRect, DockBtnImages[dhiClose]); {$IFDEF restore} // draw restore button BtnRect := GetRectOfPart(ARect, AOrientation, zpRestoreButton, AZone.HasSizer); - ACanvas.FillRect(BtnRect); - DrawButton(BtnRect, IsMouseDown, PtInRect(BtnRect, MousePos), DockBtnImages[dhiRestore]); + DrawButton(BtnRect, DockBtnImages[dhiRestore]); {$ENDIF} end;