1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 // Netscape Communications
11 // See documentation in associated header file
14 #include "gfxContext.h"
15 #include "nsSplitterFrame.h"
16 #include "nsGkAtoms.h"
17 #include "nsXULElement.h"
18 #include "nsPresContext.h"
19 #include "mozilla/dom/Document.h"
20 #include "nsNameSpaceManager.h"
21 #include "nsScrollbarButtonFrame.h"
22 #include "nsIDOMEventListener.h"
23 #include "nsICSSDeclaration.h"
24 #include "nsFrameList.h"
25 #include "nsHTMLParts.h"
26 #include "mozilla/ComputedStyle.h"
27 #include "mozilla/CSSOrderAwareFrameIterator.h"
28 #include "nsBoxLayoutState.h"
29 #include "nsContainerFrame.h"
30 #include "nsContentCID.h"
31 #include "nsLayoutUtils.h"
32 #include "nsDisplayList.h"
33 #include "nsContentUtils.h"
34 #include "nsFlexContainerFrame.h"
35 #include "mozilla/dom/Element.h"
36 #include "mozilla/dom/Event.h"
37 #include "mozilla/dom/MouseEvent.h"
38 #include "mozilla/MouseEvents.h"
39 #include "mozilla/PresShell.h"
40 #include "mozilla/UniquePtr.h"
41 #include "nsStyledElement.h"
43 using namespace mozilla
;
45 using mozilla::dom::Element
;
46 using mozilla::dom::Event
;
48 class nsSplitterInfo
{
55 nsCOMPtr
<nsIContent
> childElem
;
58 enum class ResizeType
{
59 // Resize the closest sibling in a given direction.
61 // Resize the farthest sibling in a given direction.
63 // Resize only flexible siblings in a given direction.
65 // No space should be taken out of any children in that direction.
66 // FIXME(emilio): This is a rather odd name...
68 // Only resize adjacent siblings.
70 // Don't resize anything in a given direction.
73 static ResizeType
ResizeTypeFromAttribute(const Element
& aElement
,
75 static Element::AttrValuesArray strings
[] = {
76 nsGkAtoms::farthest
, nsGkAtoms::flex
, nsGkAtoms::grow
,
77 nsGkAtoms::sibling
, nsGkAtoms::none
, nullptr};
78 switch (aElement
.FindAttrValueIn(kNameSpaceID_None
, aAttribute
, strings
,
81 return ResizeType::Farthest
;
83 return ResizeType::Flex
;
85 // Grow only applies to resizeAfter.
86 if (aAttribute
== nsGkAtoms::resizeafter
) {
87 return ResizeType::Grow
;
91 return ResizeType::Sibling
;
93 return ResizeType::None
;
97 return ResizeType::Closest
;
100 class nsSplitterFrameInner final
: public nsIDOMEventListener
{
102 virtual ~nsSplitterFrameInner();
106 NS_DECL_NSIDOMEVENTLISTENER
108 explicit nsSplitterFrameInner(nsSplitterFrame
* aSplitter
)
109 : mOuter(aSplitter
) {}
111 void Disconnect() { mOuter
= nullptr; }
113 nsresult
MouseDown(Event
* aMouseEvent
);
114 nsresult
MouseUp(Event
* aMouseEvent
);
115 nsresult
MouseMove(Event
* aMouseEvent
);
117 void MouseDrag(nsPresContext
* aPresContext
, WidgetGUIEvent
* aEvent
);
118 void MouseUp(nsPresContext
* aPresContext
, WidgetGUIEvent
* aEvent
);
120 void AdjustChildren(nsPresContext
* aPresContext
);
121 void AdjustChildren(nsPresContext
* aPresContext
,
122 nsTArray
<nsSplitterInfo
>& aChildInfos
,
125 void AddRemoveSpace(nscoord aDiff
, nsTArray
<nsSplitterInfo
>& aChildInfos
,
126 int32_t& aSpaceLeft
);
128 void ResizeChildTo(nscoord
& aDiff
);
133 void RemoveListener();
135 enum class State
{ Open
, CollapsedBefore
, CollapsedAfter
, Dragging
};
136 enum CollapseDirection
{ Before
, After
};
138 ResizeType
GetResizeBefore();
139 ResizeType
GetResizeAfter();
142 bool SupportsCollapseDirection(CollapseDirection aDirection
);
145 void SetPreferredSize(nsBoxLayoutState
& aState
, nsIFrame
* aChildBox
,
146 bool aIsHorizontal
, nscoord aSize
);
148 nsSplitterFrame
* mOuter
;
149 bool mDidDrag
= false;
150 nscoord mDragStart
= 0;
151 nsIFrame
* mParentBox
= nullptr;
152 bool mPressed
= false;
153 nsTArray
<nsSplitterInfo
> mChildInfosBefore
;
154 nsTArray
<nsSplitterInfo
> mChildInfosAfter
;
155 State mState
= State::Open
;
156 nscoord mSplitterPos
= 0;
157 bool mDragging
= false;
159 const Element
* SplitterElement() const {
160 return mOuter
->GetContent()->AsElement();
164 NS_IMPL_ISUPPORTS(nsSplitterFrameInner
, nsIDOMEventListener
)
166 ResizeType
nsSplitterFrameInner::GetResizeBefore() {
167 return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizebefore
);
170 ResizeType
nsSplitterFrameInner::GetResizeAfter() {
171 return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizeafter
);
174 nsSplitterFrameInner::~nsSplitterFrameInner() = default;
176 nsSplitterFrameInner::State
nsSplitterFrameInner::GetState() {
177 static Element::AttrValuesArray strings
[] = {nsGkAtoms::dragging
,
178 nsGkAtoms::collapsed
, nullptr};
179 static Element::AttrValuesArray strings_substate
[] = {
180 nsGkAtoms::before
, nsGkAtoms::after
, nullptr};
181 switch (SplitterElement()->FindAttrValueIn(
182 kNameSpaceID_None
, nsGkAtoms::state
, strings
, eCaseMatters
)) {
184 return State::Dragging
;
186 switch (SplitterElement()->FindAttrValueIn(
187 kNameSpaceID_None
, nsGkAtoms::substate
, strings_substate
,
190 return State::CollapsedBefore
;
192 return State::CollapsedAfter
;
194 if (SupportsCollapseDirection(After
)) {
195 return State::CollapsedAfter
;
197 return State::CollapsedBefore
;
204 // NS_NewSplitterFrame
206 // Creates a new Toolbar frame and returns it
208 nsIFrame
* NS_NewSplitterFrame(PresShell
* aPresShell
, ComputedStyle
* aStyle
) {
209 return new (aPresShell
) nsSplitterFrame(aStyle
, aPresShell
->GetPresContext());
212 NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame
)
214 nsSplitterFrame::nsSplitterFrame(ComputedStyle
* aStyle
,
215 nsPresContext
* aPresContext
)
216 : nsBoxFrame(aStyle
, aPresContext
, kClassID
) {}
218 void nsSplitterFrame::DestroyFrom(nsIFrame
* aDestructRoot
,
219 PostDestroyData
& aPostDestroyData
) {
221 mInner
->RemoveListener();
222 mInner
->Disconnect();
225 nsBoxFrame::DestroyFrom(aDestructRoot
, aPostDestroyData
);
228 nsresult
nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID
,
232 nsBoxFrame::AttributeChanged(aNameSpaceID
, aAttribute
, aModType
);
233 if (aAttribute
== nsGkAtoms::state
) {
234 mInner
->UpdateState();
241 * Initialize us. If we are in a box get our alignment so we know what direction
244 void nsSplitterFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
245 nsIFrame
* aPrevInFlow
) {
247 mInner
= new nsSplitterFrameInner(this);
249 nsBoxFrame::Init(aContent
, aParent
, aPrevInFlow
);
251 mInner
->AddListener();
252 mInner
->mParentBox
= nullptr;
255 static bool IsValidParentBox(nsIFrame
* aFrame
) {
256 return aFrame
->IsXULBoxFrame() || aFrame
->IsFlexContainerFrame();
259 static nsIFrame
* GetValidParentBox(nsIFrame
* aChild
) {
260 return aChild
->GetParent() && IsValidParentBox(aChild
->GetParent())
261 ? aChild
->GetParent()
266 nsSplitterFrame::DoXULLayout(nsBoxLayoutState
& aState
) {
267 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
268 mInner
->mParentBox
= GetValidParentBox(this);
269 mInner
->UpdateState();
272 return nsBoxFrame::DoXULLayout(aState
);
275 static bool SplitterIsHorizontal(const nsIFrame
* aParentBox
) {
276 // If our parent is horizontal, the splitter is vertical and vice-versa.
277 if (aParentBox
->IsXULBoxFrame()) {
278 return !aParentBox
->HasAnyStateBits(NS_STATE_IS_HORIZONTAL
);
280 MOZ_ASSERT(aParentBox
->IsFlexContainerFrame());
281 const FlexboxAxisInfo
info(aParentBox
);
282 return !info
.mIsRowOriented
;
285 void nsSplitterFrame::GetInitialOrientation(bool& aIsHorizontal
) {
286 if (nsIFrame
* parent
= GetValidParentBox(this)) {
287 aIsHorizontal
= SplitterIsHorizontal(parent
);
289 nsBoxFrame::GetInitialOrientation(aIsHorizontal
);
294 nsSplitterFrame::HandlePress(nsPresContext
* aPresContext
,
295 WidgetGUIEvent
* aEvent
,
296 nsEventStatus
* aEventStatus
) {
301 nsSplitterFrame::HandleMultiplePress(nsPresContext
* aPresContext
,
302 WidgetGUIEvent
* aEvent
,
303 nsEventStatus
* aEventStatus
,
309 nsSplitterFrame::HandleDrag(nsPresContext
* aPresContext
, WidgetGUIEvent
* aEvent
,
310 nsEventStatus
* aEventStatus
) {
315 nsSplitterFrame::HandleRelease(nsPresContext
* aPresContext
,
316 WidgetGUIEvent
* aEvent
,
317 nsEventStatus
* aEventStatus
) {
321 void nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
322 const nsDisplayListSet
& aLists
) {
323 nsBoxFrame::BuildDisplayList(aBuilder
, aLists
);
325 // if the mouse is captured always return us as the frame.
326 if (mInner
->mDragging
&& aBuilder
->IsForEventDelivery()) {
327 // XXX It's probably better not to check visibility here, right?
328 aLists
.Outlines()->AppendNewToTop
<nsDisplayEventReceiver
>(aBuilder
, this);
333 nsresult
nsSplitterFrame::HandleEvent(nsPresContext
* aPresContext
,
334 WidgetGUIEvent
* aEvent
,
335 nsEventStatus
* aEventStatus
) {
336 NS_ENSURE_ARG_POINTER(aEventStatus
);
337 if (nsEventStatus_eConsumeNoDefault
== *aEventStatus
) {
341 AutoWeakFrame
weakFrame(this);
342 RefPtr
<nsSplitterFrameInner
> inner(mInner
);
343 switch (aEvent
->mMessage
) {
345 inner
->MouseDrag(aPresContext
, aEvent
);
349 if (aEvent
->AsMouseEvent()->mButton
== MouseButton::ePrimary
) {
350 inner
->MouseUp(aPresContext
, aEvent
);
358 NS_ENSURE_STATE(weakFrame
.IsAlive());
359 return nsBoxFrame::HandleEvent(aPresContext
, aEvent
, aEventStatus
);
362 void nsSplitterFrameInner::MouseUp(nsPresContext
* aPresContext
,
363 WidgetGUIEvent
* aEvent
) {
364 if (mDragging
&& mOuter
) {
365 AdjustChildren(aPresContext
);
367 PresShell::ReleaseCapturingContent(); // XXXndeakin is this needed?
369 State newState
= GetState();
370 // if the state is dragging then make it Open.
371 if (newState
== State::Dragging
) {
372 mOuter
->mContent
->AsElement()->SetAttr(kNameSpaceID_None
,
373 nsGkAtoms::state
, u
""_ns
, true);
378 // if we dragged then fire a command event.
380 RefPtr
<nsXULElement
> element
=
381 nsXULElement::FromNode(mOuter
->GetContent());
382 element
->DoCommand();
385 // printf("MouseUp\n");
388 mChildInfosBefore
.Clear();
389 mChildInfosAfter
.Clear();
392 void nsSplitterFrameInner::MouseDrag(nsPresContext
* aPresContext
,
393 WidgetGUIEvent
* aEvent
) {
394 if (!mDragging
|| !mOuter
) {
398 const bool isHorizontal
= !mOuter
->IsXULHorizontal();
399 nsPoint pt
= nsLayoutUtils::GetEventCoordinatesRelativeTo(
400 aEvent
, RelativeTo
{mParentBox
});
401 nscoord pos
= isHorizontal
? pt
.x
: pt
.y
;
403 // take our current position and subtract the start location,
404 // mDragStart is in parent-box relative coordinates already.
407 for (auto& info
: mChildInfosBefore
) {
408 info
.changed
= info
.current
;
411 for (auto& info
: mChildInfosAfter
) {
412 info
.changed
= info
.current
;
414 nscoord oldPos
= pos
;
418 State currentState
= GetState();
419 bool supportsBefore
= SupportsCollapseDirection(Before
);
420 bool supportsAfter
= SupportsCollapseDirection(After
);
423 mOuter
->StyleVisibility()->mDirection
== StyleDirection::Rtl
;
424 bool pastEnd
= oldPos
> 0 && oldPos
> pos
;
425 bool pastBegin
= oldPos
< 0 && oldPos
< pos
;
427 // Swap the boundary checks in RTL mode
428 std::swap(pastEnd
, pastBegin
);
430 const bool isCollapsedBefore
= pastBegin
&& supportsBefore
;
431 const bool isCollapsedAfter
= pastEnd
&& supportsAfter
;
433 // if we are in a collapsed position
434 if (isCollapsedBefore
|| isCollapsedAfter
) {
435 // and we are not collapsed then collapse
436 if (currentState
== State::Dragging
) {
438 // printf("Collapse right\n");
440 RefPtr
<Element
> outer
= mOuter
->mContent
->AsElement();
441 outer
->SetAttr(kNameSpaceID_None
, nsGkAtoms::substate
, u
"after"_ns
,
443 outer
->SetAttr(kNameSpaceID_None
, nsGkAtoms::state
, u
"collapsed"_ns
,
447 } else if (pastBegin
) {
448 // printf("Collapse left\n");
449 if (supportsBefore
) {
450 RefPtr
<Element
> outer
= mOuter
->mContent
->AsElement();
451 outer
->SetAttr(kNameSpaceID_None
, nsGkAtoms::substate
, u
"before"_ns
,
453 outer
->SetAttr(kNameSpaceID_None
, nsGkAtoms::state
, u
"collapsed"_ns
,
459 // if we are not in a collapsed position and we are not dragging make sure
461 if (currentState
!= State::Dragging
) {
462 mOuter
->mContent
->AsElement()->SetAttr(
463 kNameSpaceID_None
, nsGkAtoms::state
, u
"dragging"_ns
, true);
465 AdjustChildren(aPresContext
);
471 void nsSplitterFrameInner::AddListener() {
472 mOuter
->GetContent()->AddEventListener(u
"mouseup"_ns
, this, false, false);
473 mOuter
->GetContent()->AddEventListener(u
"mousedown"_ns
, this, false, false);
474 mOuter
->GetContent()->AddEventListener(u
"mousemove"_ns
, this, false, false);
475 mOuter
->GetContent()->AddEventListener(u
"mouseout"_ns
, this, false, false);
478 void nsSplitterFrameInner::RemoveListener() {
479 NS_ENSURE_TRUE_VOID(mOuter
);
480 mOuter
->GetContent()->RemoveEventListener(u
"mouseup"_ns
, this, false);
481 mOuter
->GetContent()->RemoveEventListener(u
"mousedown"_ns
, this, false);
482 mOuter
->GetContent()->RemoveEventListener(u
"mousemove"_ns
, this, false);
483 mOuter
->GetContent()->RemoveEventListener(u
"mouseout"_ns
, this, false);
486 nsresult
nsSplitterFrameInner::HandleEvent(dom::Event
* aEvent
) {
487 nsAutoString eventType
;
488 aEvent
->GetType(eventType
);
489 if (eventType
.EqualsLiteral("mouseup")) return MouseUp(aEvent
);
490 if (eventType
.EqualsLiteral("mousedown")) return MouseDown(aEvent
);
491 if (eventType
.EqualsLiteral("mousemove") ||
492 eventType
.EqualsLiteral("mouseout"))
493 return MouseMove(aEvent
);
495 MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
499 nsresult
nsSplitterFrameInner::MouseUp(Event
* aMouseEvent
) {
500 NS_ENSURE_TRUE(mOuter
, NS_OK
);
503 PresShell::ReleaseCapturingContent();
508 template <typename LengthLike
>
509 static nscoord
ToLengthWithFallback(const LengthLike
& aLengthLike
,
511 if (aLengthLike
.ConvertsToLength()) {
512 return aLengthLike
.ToLength();
517 template <typename LengthLike
>
518 static nsSize
ToLengthWithFallback(const LengthLike
& aWidth
,
519 const LengthLike
& aHeight
,
520 nscoord aFallback
= 0) {
521 return {ToLengthWithFallback(aWidth
, aFallback
),
522 ToLengthWithFallback(aHeight
, aFallback
)};
525 nsresult
nsSplitterFrameInner::MouseDown(Event
* aMouseEvent
) {
526 NS_ENSURE_TRUE(mOuter
, NS_OK
);
527 dom::MouseEvent
* mouseEvent
= aMouseEvent
->AsMouseEvent();
532 // only if left button
533 if (mouseEvent
->Button() != 0) {
537 if (SplitterElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::disabled
,
538 nsGkAtoms::_true
, eCaseMatters
))
541 mParentBox
= GetValidParentBox(mOuter
);
547 nsPresContext
* outerPresContext
= mOuter
->PresContext();
549 RefPtr
<gfxContext
> rc
=
550 outerPresContext
->PresShell()->CreateReferenceRenderingContext();
551 nsBoxLayoutState
state(outerPresContext
, rc
);
556 const bool isHorizontal
= !mOuter
->IsXULHorizontal();
558 const nsIContent
* outerContent
= mOuter
->GetContent();
560 const ResizeType resizeBefore
= GetResizeBefore();
561 const ResizeType resizeAfter
= GetResizeAfter();
562 const int32_t childCount
= mParentBox
->PrincipalChildList().GetLength();
564 mChildInfosBefore
.Clear();
565 mChildInfosAfter
.Clear();
568 bool foundOuter
= false;
569 CSSOrderAwareFrameIterator
iter(
570 mParentBox
, layout::kPrincipalList
,
571 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll
,
572 CSSOrderAwareFrameIterator::OrderState::Unknown
,
573 CSSOrderAwareFrameIterator::OrderingProperty::BoxOrdinalGroup
);
574 for (; !iter
.AtEnd(); iter
.Next()) {
575 nsIFrame
* childBox
= iter
.get();
576 if (childBox
== mOuter
) {
579 // We're at the beginning, nothing to do.
582 if (count
== childCount
- 1 && resizeAfter
!= ResizeType::Grow
) {
583 // If it's the last index then we need to allow for resizeafter="grow"
589 nsIContent
* content
= childBox
->GetContent();
590 const nscoord flex
= childBox
->GetXULFlex();
591 const bool isBefore
= !foundOuter
;
592 const bool isResizable
= [&] {
593 if (auto* element
= nsXULElement::FromNode(content
)) {
594 if (element
->NodeInfo()->NameAtom() == nsGkAtoms::splitter
) {
595 // skip over any splitters
599 // We need to check for hidden attribute too, since treecols with
600 // the hidden="true" attribute are not really hidden, just collapsed
601 if (element
->GetXULBoolAttr(nsGkAtoms::fixed
) ||
602 element
->GetXULBoolAttr(nsGkAtoms::hidden
)) {
607 // We need to check this here rather than in the switch before because we
608 // want `sibling` to work in the DOM order, not frame tree order.
609 if (resizeBefore
== ResizeType::Sibling
&&
610 content
->GetNextElementSibling() == outerContent
) {
613 if (resizeAfter
== ResizeType::Sibling
&&
614 content
->GetPreviousElementSibling() == outerContent
) {
618 const ResizeType resizeType
= isBefore
? resizeBefore
: resizeAfter
;
619 switch (resizeType
) {
620 case ResizeType::Grow
:
621 case ResizeType::None
:
622 case ResizeType::Sibling
:
624 case ResizeType::Flex
:
626 case ResizeType::Closest
:
627 case ResizeType::Farthest
:
639 nsSize
maxSize(NS_UNCONSTRAINEDSIZE
, NS_UNCONSTRAINEDSIZE
);
640 nsSize curSize
= childBox
->GetSize();
641 if (childBox
->IsXULBoxFrame()) {
642 minSize
= childBox
->GetXULMinSize(state
);
643 maxSize
= childBox
->GetXULMaxSize(state
);
644 prefSize
= childBox
->GetXULPrefSize(state
);
646 const auto& pos
= *childBox
->StylePosition();
647 minSize
= ToLengthWithFallback(pos
.mMinWidth
, pos
.mMinHeight
);
648 maxSize
= ToLengthWithFallback(pos
.mMaxWidth
, pos
.mMaxHeight
,
649 NS_UNCONSTRAINEDSIZE
);
650 prefSize
.width
= ToLengthWithFallback(pos
.mWidth
, curSize
.width
);
651 prefSize
.height
= ToLengthWithFallback(pos
.mHeight
, curSize
.height
);
654 maxSize
= nsIFrame::XULBoundsCheckMinMax(minSize
, maxSize
);
655 prefSize
= nsIFrame::XULBoundsCheck(minSize
, prefSize
, maxSize
);
657 nsSplitterFrame::AddXULMargin(childBox
, minSize
);
658 nsSplitterFrame::AddXULMargin(childBox
, maxSize
);
659 nsSplitterFrame::AddXULMargin(childBox
, prefSize
);
660 nsSplitterFrame::AddXULMargin(childBox
, curSize
);
662 auto& list
= isBefore
? mChildInfosBefore
: mChildInfosAfter
;
663 nsSplitterInfo
& info
= *list
.AppendElement();
664 info
.childElem
= content
;
665 info
.min
= isHorizontal
? minSize
.width
: minSize
.height
;
666 info
.max
= isHorizontal
? maxSize
.width
: maxSize
.height
;
667 info
.pref
= isHorizontal
? prefSize
.width
: prefSize
.height
;
668 info
.current
= info
.changed
= isHorizontal
? curSize
.width
: curSize
.height
;
677 const bool reverseDirection
= [&] {
678 if (mParentBox
->IsXULBoxFrame()) {
679 return !mParentBox
->IsXULNormalDirection();
681 MOZ_ASSERT(mParentBox
->IsFlexContainerFrame());
682 const FlexboxAxisInfo
info(mParentBox
);
683 if (!info
.mIsRowOriented
) {
684 return info
.mIsMainAxisReversed
;
687 mParentBox
->StyleVisibility()->mDirection
== StyleDirection::Rtl
;
688 return info
.mIsMainAxisReversed
!= rtl
;
691 if (reverseDirection
) {
692 // The before array is really the after array, and the order needs to be
693 // reversed. First reverse both arrays.
694 mChildInfosBefore
.Reverse();
695 mChildInfosAfter
.Reverse();
697 // Now swap the two arrays.
698 std::swap(mChildInfosBefore
, mChildInfosAfter
);
701 // if resizebefore is not Farthest, reverse the list because the first child
702 // in the list is the farthest, and we want the first child to be the closest.
703 if (resizeBefore
!= ResizeType::Farthest
) {
704 mChildInfosBefore
.Reverse();
707 // if the resizeafter is the Farthest we must reverse the list because the
708 // first child in the list is the closest we want the first child to be the
710 if (resizeAfter
== ResizeType::Farthest
) {
711 mChildInfosAfter
.Reverse();
716 nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent
, mParentBox
);
719 mSplitterPos
= mOuter
->mRect
.x
;
722 mSplitterPos
= mOuter
->mRect
.y
;
727 // printf("Pressed mDragStart=%d\n",mDragStart);
729 PresShell::SetCapturingContent(mOuter
->GetContent(),
730 CaptureFlags::IgnoreAllowedState
);
735 nsresult
nsSplitterFrameInner::MouseMove(Event
* aMouseEvent
) {
736 NS_ENSURE_TRUE(mOuter
, NS_OK
);
745 nsCOMPtr
<nsIDOMEventListener
> kungfuDeathGrip(this);
746 mOuter
->mContent
->AsElement()->SetAttr(kNameSpaceID_None
, nsGkAtoms::state
,
747 u
"dragging"_ns
, true);
755 bool nsSplitterFrameInner::SupportsCollapseDirection(
756 nsSplitterFrameInner::CollapseDirection aDirection
) {
757 static Element::AttrValuesArray strings
[] = {
758 nsGkAtoms::before
, nsGkAtoms::after
, nsGkAtoms::both
, nullptr};
760 switch (SplitterElement()->FindAttrValueIn(
761 kNameSpaceID_None
, nsGkAtoms::collapse
, strings
, eCaseMatters
)) {
763 return (aDirection
== Before
);
765 return (aDirection
== After
);
773 void nsSplitterFrameInner::UpdateState() {
774 // State Transitions:
776 // Open -> CollapsedBefore
777 // Open -> CollapsedAfter
778 // CollapsedBefore -> Open
779 // CollapsedBefore -> Dragging
780 // CollapsedAfter -> Open
781 // CollapsedAfter -> Dragging
783 // Dragging -> CollapsedBefore (auto collapse)
784 // Dragging -> CollapsedAfter (auto collapse)
786 State newState
= GetState();
788 if (newState
== mState
) {
793 if ((SupportsCollapseDirection(Before
) || SupportsCollapseDirection(After
)) &&
794 IsValidParentBox(mOuter
->GetParent())) {
795 // Find the splitter's immediate sibling.
797 newState
== State::CollapsedBefore
|| mState
== State::CollapsedBefore
;
798 nsIFrame
* splitterSibling
=
799 nsBoxFrame::SlowOrdinalGroupAwareSibling(mOuter
, !prev
);
800 if (splitterSibling
) {
801 nsCOMPtr
<nsIContent
> sibling
= splitterSibling
->GetContent();
802 if (sibling
&& sibling
->IsElement()) {
803 if (mState
== State::CollapsedBefore
||
804 mState
== State::CollapsedAfter
) {
805 // CollapsedBefore -> Open
806 // CollapsedBefore -> Dragging
807 // CollapsedAfter -> Open
808 // CollapsedAfter -> Dragging
809 nsContentUtils::AddScriptRunner(new nsUnsetAttrRunnable(
810 sibling
->AsElement(), nsGkAtoms::collapsed
));
811 } else if ((mState
== State::Open
|| mState
== State::Dragging
) &&
812 (newState
== State::CollapsedBefore
||
813 newState
== State::CollapsedAfter
)) {
814 // Open -> CollapsedBefore / CollapsedAfter
815 // Dragging -> CollapsedBefore / CollapsedAfter
816 nsContentUtils::AddScriptRunner(new nsSetAttrRunnable(
817 sibling
->AsElement(), nsGkAtoms::collapsed
, u
"true"_ns
));
825 void nsSplitterFrameInner::EnsureOrient() {
826 if (SplitterIsHorizontal(mParentBox
))
827 mOuter
->AddStateBits(NS_STATE_IS_HORIZONTAL
);
829 mOuter
->RemoveStateBits(NS_STATE_IS_HORIZONTAL
);
832 void nsSplitterFrameInner::AdjustChildren(nsPresContext
* aPresContext
) {
834 const bool isHorizontal
= !mOuter
->IsXULHorizontal();
836 AdjustChildren(aPresContext
, mChildInfosBefore
, isHorizontal
);
837 AdjustChildren(aPresContext
, mChildInfosAfter
, isHorizontal
);
840 static nsIFrame
* GetChildBoxForContent(nsIFrame
* aParentBox
,
841 nsIContent
* aContent
) {
842 // XXX Can this use GetPrimaryFrame?
843 for (nsIFrame
* f
: aParentBox
->PrincipalChildList()) {
844 if (f
->GetContent() == aContent
) {
851 void nsSplitterFrameInner::AdjustChildren(nsPresContext
* aPresContext
,
852 nsTArray
<nsSplitterInfo
>& aChildInfos
,
853 bool aIsHorizontal
) {
854 /// printf("------- AdjustChildren------\n");
856 nsBoxLayoutState
state(aPresContext
);
858 for (auto& info
: aChildInfos
) {
859 nscoord newPref
= info
.pref
+ (info
.changed
- info
.current
);
860 if (nsIFrame
* childBox
=
861 GetChildBoxForContent(mParentBox
, info
.childElem
)) {
862 SetPreferredSize(state
, childBox
, aIsHorizontal
, newPref
);
867 void nsSplitterFrameInner::SetPreferredSize(nsBoxLayoutState
& aState
,
869 bool aIsHorizontal
, nscoord aSize
) {
870 nsMargin
margin(0, 0, 0, 0);
871 aChildBox
->GetXULMargin(margin
);
874 aSize
-= (margin
.left
+ margin
.right
);
876 aSize
-= (margin
.top
+ margin
.bottom
);
879 RefPtr element
= nsStyledElement::FromNode(aChildBox
->GetContent());
884 // We set both the attribute and the CSS value, so that XUL persist="" keeps
885 // working, see bug 1790712.
887 int32_t pixels
= aSize
/ AppUnitsPerCSSPixel();
888 nsAutoString attrValue
;
889 attrValue
.AppendInt(pixels
);
890 element
->SetAttr(aIsHorizontal
? nsGkAtoms::width
: nsGkAtoms::height
,
891 attrValue
, IgnoreErrors());
893 nsCOMPtr
<nsICSSDeclaration
> decl
= element
->Style();
895 nsAutoCString cssValue
;
896 cssValue
.AppendInt(pixels
);
897 cssValue
.AppendLiteral("px");
898 decl
->SetProperty(aIsHorizontal
? "width"_ns
: "height"_ns
, cssValue
, ""_ns
,
902 void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff
,
903 nsTArray
<nsSplitterInfo
>& aChildInfos
,
904 int32_t& aSpaceLeft
) {
907 for (auto& info
: aChildInfos
) {
908 nscoord min
= info
.min
;
909 nscoord max
= info
.max
;
910 nscoord
& c
= info
.changed
;
912 // figure our how much space to add or remove
913 if (c
+ aDiff
< min
) {
916 } else if (c
+ aDiff
> max
) {
924 // there is not space left? We are done
934 * Ok if we want to resize a child we will know the actual size in pixels we
935 * want it to be. This is not the preferred size. But the only way we can change
936 * a child is by manipulating its preferred size. So give the actual pixel size
937 * this method will figure out the preferred size and set it.
940 void nsSplitterFrameInner::ResizeChildTo(nscoord
& aDiff
) {
941 nscoord spaceLeft
= 0;
943 if (!mChildInfosBefore
.IsEmpty()) {
944 AddRemoveSpace(aDiff
, mChildInfosBefore
, spaceLeft
);
945 // If there is any space left over remove it from the diff we were
950 AddRemoveSpace(-aDiff
, mChildInfosAfter
, spaceLeft
);
952 if (spaceLeft
!= 0 && !mChildInfosAfter
.IsEmpty()) {
954 AddRemoveSpace(spaceLeft
, mChildInfosBefore
, spaceLeft
);