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 "LayoutConstants.h"
15 #include "SimpleXULLeafFrame.h"
16 #include "gfxContext.h"
17 #include "mozilla/ReflowInput.h"
18 #include "nsSplitterFrame.h"
19 #include "nsGkAtoms.h"
20 #include "nsXULElement.h"
21 #include "nsPresContext.h"
22 #include "mozilla/dom/Document.h"
23 #include "nsNameSpaceManager.h"
24 #include "nsScrollbarButtonFrame.h"
25 #include "nsIDOMEventListener.h"
26 #include "nsICSSDeclaration.h"
27 #include "nsFrameList.h"
28 #include "nsHTMLParts.h"
29 #include "mozilla/ComputedStyle.h"
30 #include "mozilla/CSSOrderAwareFrameIterator.h"
31 #include "nsContainerFrame.h"
32 #include "nsLayoutUtils.h"
33 #include "nsDisplayList.h"
34 #include "nsContentUtils.h"
35 #include "nsFlexContainerFrame.h"
36 #include "mozilla/dom/Element.h"
37 #include "mozilla/dom/Event.h"
38 #include "mozilla/dom/MouseEvent.h"
39 #include "mozilla/MouseEvents.h"
40 #include "mozilla/PresShell.h"
41 #include "mozilla/UniquePtr.h"
42 #include "nsStyledElement.h"
44 using namespace mozilla
;
46 using mozilla::dom::Element
;
47 using mozilla::dom::Event
;
49 class nsSplitterInfo
{
56 nsCOMPtr
<nsIContent
> childElem
;
59 enum class ResizeType
{
60 // Resize the closest sibling in a given direction.
62 // Resize the farthest sibling in a given direction.
64 // Resize only flexible siblings in a given direction.
66 // No space should be taken out of any children in that direction.
67 // FIXME(emilio): This is a rather odd name...
69 // Only resize adjacent siblings.
71 // Don't resize anything in a given direction.
74 static ResizeType
ResizeTypeFromAttribute(const Element
& aElement
,
76 static Element::AttrValuesArray strings
[] = {
77 nsGkAtoms::farthest
, nsGkAtoms::flex
, nsGkAtoms::grow
,
78 nsGkAtoms::sibling
, nsGkAtoms::none
, nullptr};
79 switch (aElement
.FindAttrValueIn(kNameSpaceID_None
, aAttribute
, strings
,
82 return ResizeType::Farthest
;
84 return ResizeType::Flex
;
86 // Grow only applies to resizeAfter.
87 if (aAttribute
== nsGkAtoms::resizeafter
) {
88 return ResizeType::Grow
;
92 return ResizeType::Sibling
;
94 return ResizeType::None
;
98 return ResizeType::Closest
;
101 class nsSplitterFrameInner final
: public nsIDOMEventListener
{
103 virtual ~nsSplitterFrameInner();
107 NS_DECL_NSIDOMEVENTLISTENER
109 explicit nsSplitterFrameInner(nsSplitterFrame
* aSplitter
)
110 : mOuter(aSplitter
) {}
112 void Disconnect() { mOuter
= nullptr; }
114 nsresult
MouseDown(Event
* aMouseEvent
);
115 nsresult
MouseUp(Event
* aMouseEvent
);
116 nsresult
MouseMove(Event
* aMouseEvent
);
118 void MouseDrag(nsPresContext
* aPresContext
, WidgetGUIEvent
* aEvent
);
119 void MouseUp(nsPresContext
* aPresContext
, WidgetGUIEvent
* aEvent
);
121 void AdjustChildren(nsPresContext
* aPresContext
);
122 void AdjustChildren(nsPresContext
* aPresContext
,
123 nsTArray
<nsSplitterInfo
>& aChildInfos
,
126 void AddRemoveSpace(nscoord aDiff
, nsTArray
<nsSplitterInfo
>& aChildInfos
,
127 int32_t& aSpaceLeft
);
129 void ResizeChildTo(nscoord
& aDiff
);
134 void RemoveListener();
136 enum class State
{ Open
, CollapsedBefore
, CollapsedAfter
, Dragging
};
137 enum CollapseDirection
{ Before
, After
};
139 ResizeType
GetResizeBefore();
140 ResizeType
GetResizeAfter();
143 bool SupportsCollapseDirection(CollapseDirection aDirection
);
146 void SetPreferredSize(nsIFrame
* aChildBox
, 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 : SimpleXULLeafFrame(aStyle
, aPresContext
, kClassID
) {}
218 void nsSplitterFrame::Destroy(DestroyContext
& aContext
) {
220 mInner
->RemoveListener();
221 mInner
->Disconnect();
224 SimpleXULLeafFrame::Destroy(aContext
);
227 nsresult
nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID
,
231 SimpleXULLeafFrame::AttributeChanged(aNameSpaceID
, aAttribute
, aModType
);
232 if (aAttribute
== nsGkAtoms::state
) {
233 mInner
->UpdateState();
240 * Initialize us. If we are in a box get our alignment so we know what direction
243 void nsSplitterFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
244 nsIFrame
* aPrevInFlow
) {
246 mInner
= new nsSplitterFrameInner(this);
248 SimpleXULLeafFrame::Init(aContent
, aParent
, aPrevInFlow
);
250 mInner
->AddListener();
251 mInner
->mParentBox
= nullptr;
254 static bool IsValidParentBox(nsIFrame
* aFrame
) {
255 return aFrame
->IsFlexContainerFrame();
258 static nsIFrame
* GetValidParentBox(nsIFrame
* aChild
) {
259 return aChild
->GetParent() && IsValidParentBox(aChild
->GetParent())
260 ? aChild
->GetParent()
264 void nsSplitterFrame::Reflow(nsPresContext
* aPresContext
,
265 ReflowOutput
& aDesiredSize
,
266 const ReflowInput
& aReflowInput
,
267 nsReflowStatus
& aStatus
) {
268 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
269 mInner
->mParentBox
= GetValidParentBox(this);
270 mInner
->UpdateState();
272 return SimpleXULLeafFrame::Reflow(aPresContext
, aDesiredSize
, aReflowInput
,
276 static bool SplitterIsHorizontal(const nsIFrame
* aParentBox
) {
277 // If our parent is horizontal, the splitter is vertical and vice-versa.
278 MOZ_ASSERT(aParentBox
->IsFlexContainerFrame());
279 const FlexboxAxisInfo
info(aParentBox
);
280 return !info
.mIsRowOriented
;
284 nsSplitterFrame::HandlePress(nsPresContext
* aPresContext
,
285 WidgetGUIEvent
* aEvent
,
286 nsEventStatus
* aEventStatus
) {
291 nsSplitterFrame::HandleMultiplePress(nsPresContext
* aPresContext
,
292 WidgetGUIEvent
* aEvent
,
293 nsEventStatus
* aEventStatus
,
299 nsSplitterFrame::HandleDrag(nsPresContext
* aPresContext
, WidgetGUIEvent
* aEvent
,
300 nsEventStatus
* aEventStatus
) {
305 nsSplitterFrame::HandleRelease(nsPresContext
* aPresContext
,
306 WidgetGUIEvent
* aEvent
,
307 nsEventStatus
* aEventStatus
) {
311 void nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
312 const nsDisplayListSet
& aLists
) {
313 SimpleXULLeafFrame::BuildDisplayList(aBuilder
, aLists
);
315 // if the mouse is captured always return us as the frame.
316 if (mInner
->mDragging
&& aBuilder
->IsForEventDelivery()) {
317 // XXX It's probably better not to check visibility here, right?
318 aLists
.Outlines()->AppendNewToTop
<nsDisplayEventReceiver
>(aBuilder
, this);
323 nsresult
nsSplitterFrame::HandleEvent(nsPresContext
* aPresContext
,
324 WidgetGUIEvent
* aEvent
,
325 nsEventStatus
* aEventStatus
) {
326 NS_ENSURE_ARG_POINTER(aEventStatus
);
327 if (nsEventStatus_eConsumeNoDefault
== *aEventStatus
) {
331 AutoWeakFrame
weakFrame(this);
332 RefPtr
<nsSplitterFrameInner
> inner(mInner
);
333 switch (aEvent
->mMessage
) {
335 inner
->MouseDrag(aPresContext
, aEvent
);
339 if (aEvent
->AsMouseEvent()->mButton
== MouseButton::ePrimary
) {
340 inner
->MouseUp(aPresContext
, aEvent
);
348 NS_ENSURE_STATE(weakFrame
.IsAlive());
349 return SimpleXULLeafFrame::HandleEvent(aPresContext
, aEvent
, aEventStatus
);
352 void nsSplitterFrameInner::MouseUp(nsPresContext
* aPresContext
,
353 WidgetGUIEvent
* aEvent
) {
354 if (mDragging
&& mOuter
) {
355 AdjustChildren(aPresContext
);
357 PresShell::ReleaseCapturingContent(); // XXXndeakin is this needed?
359 State newState
= GetState();
360 // if the state is dragging then make it Open.
361 if (newState
== State::Dragging
) {
362 mOuter
->mContent
->AsElement()->SetAttr(kNameSpaceID_None
,
363 nsGkAtoms::state
, u
""_ns
, true);
368 // if we dragged then fire a command event.
370 RefPtr
<nsXULElement
> element
=
371 nsXULElement::FromNode(mOuter
->GetContent());
372 element
->DoCommand();
375 // printf("MouseUp\n");
378 mChildInfosBefore
.Clear();
379 mChildInfosAfter
.Clear();
382 void nsSplitterFrameInner::MouseDrag(nsPresContext
* aPresContext
,
383 WidgetGUIEvent
* aEvent
) {
384 if (!mDragging
|| !mOuter
) {
388 const bool isHorizontal
= !mOuter
->IsHorizontal();
389 nsPoint pt
= nsLayoutUtils::GetEventCoordinatesRelativeTo(
390 aEvent
, RelativeTo
{mParentBox
});
391 nscoord pos
= isHorizontal
? pt
.x
: pt
.y
;
393 // take our current position and subtract the start location,
394 // mDragStart is in parent-box relative coordinates already.
397 for (auto& info
: mChildInfosBefore
) {
398 info
.changed
= info
.current
;
401 for (auto& info
: mChildInfosAfter
) {
402 info
.changed
= info
.current
;
404 nscoord oldPos
= pos
;
408 State currentState
= GetState();
409 bool supportsBefore
= SupportsCollapseDirection(Before
);
410 bool supportsAfter
= SupportsCollapseDirection(After
);
413 mOuter
->StyleVisibility()->mDirection
== StyleDirection::Rtl
;
414 bool pastEnd
= oldPos
> 0 && oldPos
> pos
;
415 bool pastBegin
= oldPos
< 0 && oldPos
< pos
;
417 // Swap the boundary checks in RTL mode
418 std::swap(pastEnd
, pastBegin
);
420 const bool isCollapsedBefore
= pastBegin
&& supportsBefore
;
421 const bool isCollapsedAfter
= pastEnd
&& supportsAfter
;
423 // if we are in a collapsed position
424 if (isCollapsedBefore
|| isCollapsedAfter
) {
425 // and we are not collapsed then collapse
426 if (currentState
== State::Dragging
) {
428 // printf("Collapse right\n");
430 RefPtr
<Element
> outer
= mOuter
->mContent
->AsElement();
431 outer
->SetAttr(kNameSpaceID_None
, nsGkAtoms::substate
, u
"after"_ns
,
433 outer
->SetAttr(kNameSpaceID_None
, nsGkAtoms::state
, u
"collapsed"_ns
,
437 } else if (pastBegin
) {
438 // printf("Collapse left\n");
439 if (supportsBefore
) {
440 RefPtr
<Element
> outer
= mOuter
->mContent
->AsElement();
441 outer
->SetAttr(kNameSpaceID_None
, nsGkAtoms::substate
, u
"before"_ns
,
443 outer
->SetAttr(kNameSpaceID_None
, nsGkAtoms::state
, u
"collapsed"_ns
,
449 // if we are not in a collapsed position and we are not dragging make sure
451 if (currentState
!= State::Dragging
) {
452 mOuter
->mContent
->AsElement()->SetAttr(
453 kNameSpaceID_None
, nsGkAtoms::state
, u
"dragging"_ns
, true);
455 AdjustChildren(aPresContext
);
461 void nsSplitterFrameInner::AddListener() {
462 mOuter
->GetContent()->AddEventListener(u
"mouseup"_ns
, this, false, false);
463 mOuter
->GetContent()->AddEventListener(u
"mousedown"_ns
, this, false, false);
464 mOuter
->GetContent()->AddEventListener(u
"mousemove"_ns
, this, false, false);
465 mOuter
->GetContent()->AddEventListener(u
"mouseout"_ns
, this, false, false);
468 void nsSplitterFrameInner::RemoveListener() {
469 NS_ENSURE_TRUE_VOID(mOuter
);
470 mOuter
->GetContent()->RemoveEventListener(u
"mouseup"_ns
, this, false);
471 mOuter
->GetContent()->RemoveEventListener(u
"mousedown"_ns
, this, false);
472 mOuter
->GetContent()->RemoveEventListener(u
"mousemove"_ns
, this, false);
473 mOuter
->GetContent()->RemoveEventListener(u
"mouseout"_ns
, this, false);
476 nsresult
nsSplitterFrameInner::HandleEvent(dom::Event
* aEvent
) {
477 nsAutoString eventType
;
478 aEvent
->GetType(eventType
);
479 if (eventType
.EqualsLiteral("mouseup")) return MouseUp(aEvent
);
480 if (eventType
.EqualsLiteral("mousedown")) return MouseDown(aEvent
);
481 if (eventType
.EqualsLiteral("mousemove") ||
482 eventType
.EqualsLiteral("mouseout"))
483 return MouseMove(aEvent
);
485 MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
489 nsresult
nsSplitterFrameInner::MouseUp(Event
* aMouseEvent
) {
490 NS_ENSURE_TRUE(mOuter
, NS_OK
);
493 PresShell::ReleaseCapturingContent();
498 template <typename LengthLike
>
499 static nscoord
ToLengthWithFallback(const LengthLike
& aLengthLike
,
501 if (aLengthLike
.ConvertsToLength()) {
502 return aLengthLike
.ToLength();
507 template <typename LengthLike
>
508 static nsSize
ToLengthWithFallback(const LengthLike
& aWidth
,
509 const LengthLike
& aHeight
,
510 nscoord aFallback
= 0) {
511 return {ToLengthWithFallback(aWidth
, aFallback
),
512 ToLengthWithFallback(aHeight
, aFallback
)};
515 static void ApplyMargin(nsSize
& aSize
, const nsMargin
& aMargin
) {
516 if (aSize
.width
!= NS_UNCONSTRAINEDSIZE
) {
517 aSize
.width
+= aMargin
.LeftRight();
519 if (aSize
.height
!= NS_UNCONSTRAINEDSIZE
) {
520 aSize
.height
+= aMargin
.TopBottom();
524 nsresult
nsSplitterFrameInner::MouseDown(Event
* aMouseEvent
) {
525 NS_ENSURE_TRUE(mOuter
, NS_OK
);
526 dom::MouseEvent
* mouseEvent
= aMouseEvent
->AsMouseEvent();
531 // only if left button
532 if (mouseEvent
->Button() != 0) {
536 if (SplitterElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::disabled
,
537 nsGkAtoms::_true
, eCaseMatters
))
540 mParentBox
= GetValidParentBox(mOuter
);
549 const bool isHorizontal
= !mOuter
->IsHorizontal();
551 const nsIContent
* outerContent
= mOuter
->GetContent();
553 const ResizeType resizeBefore
= GetResizeBefore();
554 const ResizeType resizeAfter
= GetResizeAfter();
555 const int32_t childCount
= mParentBox
->PrincipalChildList().GetLength();
557 mChildInfosBefore
.Clear();
558 mChildInfosAfter
.Clear();
561 bool foundOuter
= false;
562 CSSOrderAwareFrameIterator
iter(
563 mParentBox
, FrameChildListID::Principal
,
564 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll
,
565 CSSOrderAwareFrameIterator::OrderState::Unknown
,
566 CSSOrderAwareFrameIterator::OrderingProperty::Order
);
567 for (; !iter
.AtEnd(); iter
.Next()) {
568 nsIFrame
* childBox
= iter
.get();
569 if (childBox
== mOuter
) {
572 // We're at the beginning, nothing to do.
575 if (count
== childCount
- 1 && resizeAfter
!= ResizeType::Grow
) {
576 // If it's the last index then we need to allow for resizeafter="grow"
582 nsIContent
* content
= childBox
->GetContent();
583 // XXX flex seems untested, as it uses mBoxFlex rather than actual flexbox
585 const nscoord flex
= childBox
->StyleXUL()->mBoxFlex
;
586 const bool isBefore
= !foundOuter
;
587 const bool isResizable
= [&] {
588 if (auto* element
= nsXULElement::FromNode(content
)) {
589 if (element
->NodeInfo()->NameAtom() == nsGkAtoms::splitter
) {
590 // skip over any splitters
594 // We need to check for hidden attribute too, since treecols with
595 // the hidden="true" attribute are not really hidden, just collapsed
596 if (element
->GetXULBoolAttr(nsGkAtoms::fixed
) ||
597 element
->GetXULBoolAttr(nsGkAtoms::hidden
)) {
602 // We need to check this here rather than in the switch before because we
603 // want `sibling` to work in the DOM order, not frame tree order.
604 if (resizeBefore
== ResizeType::Sibling
&&
605 content
->GetNextElementSibling() == outerContent
) {
608 if (resizeAfter
== ResizeType::Sibling
&&
609 content
->GetPreviousElementSibling() == outerContent
) {
613 const ResizeType resizeType
= isBefore
? resizeBefore
: resizeAfter
;
614 switch (resizeType
) {
615 case ResizeType::Grow
:
616 case ResizeType::None
:
617 case ResizeType::Sibling
:
619 case ResizeType::Flex
:
621 case ResizeType::Closest
:
622 case ResizeType::Farthest
:
632 nsSize curSize
= childBox
->GetSize();
633 const auto& pos
= *childBox
->StylePosition();
634 nsSize minSize
= ToLengthWithFallback(pos
.mMinWidth
, pos
.mMinHeight
);
635 nsSize maxSize
= ToLengthWithFallback(pos
.mMaxWidth
, pos
.mMaxHeight
,
636 NS_UNCONSTRAINEDSIZE
);
637 nsSize
prefSize(ToLengthWithFallback(pos
.mWidth
, curSize
.width
),
638 ToLengthWithFallback(pos
.mHeight
, curSize
.height
));
640 maxSize
.width
= std::max(maxSize
.width
, minSize
.width
);
641 maxSize
.height
= std::max(maxSize
.height
, minSize
.height
);
643 NS_CSS_MINMAX(prefSize
.width
, minSize
.width
, maxSize
.width
);
645 NS_CSS_MINMAX(prefSize
.height
, minSize
.height
, maxSize
.height
);
648 childBox
->StyleMargin()->GetMargin(m
);
650 ApplyMargin(curSize
, m
);
651 ApplyMargin(minSize
, m
);
652 ApplyMargin(maxSize
, m
);
653 ApplyMargin(prefSize
, m
);
655 auto& list
= isBefore
? mChildInfosBefore
: mChildInfosAfter
;
656 nsSplitterInfo
& info
= *list
.AppendElement();
657 info
.childElem
= content
;
658 info
.min
= isHorizontal
? minSize
.width
: minSize
.height
;
659 info
.max
= isHorizontal
? maxSize
.width
: maxSize
.height
;
660 info
.pref
= isHorizontal
? prefSize
.width
: prefSize
.height
;
661 info
.current
= info
.changed
= isHorizontal
? curSize
.width
: curSize
.height
;
670 const bool reverseDirection
= [&] {
671 MOZ_ASSERT(mParentBox
->IsFlexContainerFrame());
672 const FlexboxAxisInfo
info(mParentBox
);
673 if (!info
.mIsRowOriented
) {
674 return info
.mIsMainAxisReversed
;
677 mParentBox
->StyleVisibility()->mDirection
== StyleDirection::Rtl
;
678 return info
.mIsMainAxisReversed
!= rtl
;
681 if (reverseDirection
) {
682 // The before array is really the after array, and the order needs to be
683 // reversed. First reverse both arrays.
684 mChildInfosBefore
.Reverse();
685 mChildInfosAfter
.Reverse();
687 // Now swap the two arrays.
688 std::swap(mChildInfosBefore
, mChildInfosAfter
);
691 // if resizebefore is not Farthest, reverse the list because the first child
692 // in the list is the farthest, and we want the first child to be the closest.
693 if (resizeBefore
!= ResizeType::Farthest
) {
694 mChildInfosBefore
.Reverse();
697 // if the resizeafter is the Farthest we must reverse the list because the
698 // first child in the list is the closest we want the first child to be the
700 if (resizeAfter
== ResizeType::Farthest
) {
701 mChildInfosAfter
.Reverse();
706 nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent
, mParentBox
);
709 mSplitterPos
= mOuter
->mRect
.x
;
712 mSplitterPos
= mOuter
->mRect
.y
;
717 // printf("Pressed mDragStart=%d\n",mDragStart);
719 PresShell::SetCapturingContent(mOuter
->GetContent(),
720 CaptureFlags::IgnoreAllowedState
);
725 nsresult
nsSplitterFrameInner::MouseMove(Event
* aMouseEvent
) {
726 NS_ENSURE_TRUE(mOuter
, NS_OK
);
735 nsCOMPtr
<nsIDOMEventListener
> kungfuDeathGrip(this);
736 mOuter
->mContent
->AsElement()->SetAttr(kNameSpaceID_None
, nsGkAtoms::state
,
737 u
"dragging"_ns
, true);
745 bool nsSplitterFrameInner::SupportsCollapseDirection(
746 nsSplitterFrameInner::CollapseDirection aDirection
) {
747 static Element::AttrValuesArray strings
[] = {
748 nsGkAtoms::before
, nsGkAtoms::after
, nsGkAtoms::both
, nullptr};
750 switch (SplitterElement()->FindAttrValueIn(
751 kNameSpaceID_None
, nsGkAtoms::collapse
, strings
, eCaseMatters
)) {
753 return (aDirection
== Before
);
755 return (aDirection
== After
);
763 static nsIFrame
* SlowOrderAwareSibling(nsIFrame
* aBox
, bool aNext
) {
764 nsIFrame
* parent
= aBox
->GetParent();
768 CSSOrderAwareFrameIterator
iter(
769 parent
, FrameChildListID::Principal
,
770 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll
,
771 CSSOrderAwareFrameIterator::OrderState::Unknown
,
772 CSSOrderAwareFrameIterator::OrderingProperty::Order
);
774 nsIFrame
* prevSibling
= nullptr;
775 for (; !iter
.AtEnd(); iter
.Next()) {
776 nsIFrame
* current
= iter
.get();
777 if (!aNext
&& current
== aBox
) {
780 if (aNext
&& prevSibling
== aBox
) {
783 prevSibling
= current
;
788 void nsSplitterFrameInner::UpdateState() {
789 // State Transitions:
791 // Open -> CollapsedBefore
792 // Open -> CollapsedAfter
793 // CollapsedBefore -> Open
794 // CollapsedBefore -> Dragging
795 // CollapsedAfter -> Open
796 // CollapsedAfter -> Dragging
798 // Dragging -> CollapsedBefore (auto collapse)
799 // Dragging -> CollapsedAfter (auto collapse)
801 State newState
= GetState();
803 if (newState
== mState
) {
808 if ((SupportsCollapseDirection(Before
) || SupportsCollapseDirection(After
)) &&
809 IsValidParentBox(mOuter
->GetParent())) {
810 // Find the splitter's immediate sibling.
812 newState
== State::CollapsedBefore
|| mState
== State::CollapsedBefore
;
813 nsIFrame
* splitterSibling
= SlowOrderAwareSibling(mOuter
, !prev
);
814 if (splitterSibling
) {
815 nsCOMPtr
<nsIContent
> sibling
= splitterSibling
->GetContent();
816 if (sibling
&& sibling
->IsElement()) {
817 if (mState
== State::CollapsedBefore
||
818 mState
== State::CollapsedAfter
) {
819 // CollapsedBefore -> Open
820 // CollapsedBefore -> Dragging
821 // CollapsedAfter -> Open
822 // CollapsedAfter -> Dragging
823 nsContentUtils::AddScriptRunner(new nsUnsetAttrRunnable(
824 sibling
->AsElement(), nsGkAtoms::collapsed
));
825 } else if ((mState
== State::Open
|| mState
== State::Dragging
) &&
826 (newState
== State::CollapsedBefore
||
827 newState
== State::CollapsedAfter
)) {
828 // Open -> CollapsedBefore / CollapsedAfter
829 // Dragging -> CollapsedBefore / CollapsedAfter
830 nsContentUtils::AddScriptRunner(new nsSetAttrRunnable(
831 sibling
->AsElement(), nsGkAtoms::collapsed
, u
"true"_ns
));
839 void nsSplitterFrameInner::EnsureOrient() {
840 mOuter
->mIsHorizontal
= SplitterIsHorizontal(mParentBox
);
843 void nsSplitterFrameInner::AdjustChildren(nsPresContext
* aPresContext
) {
845 const bool isHorizontal
= !mOuter
->IsHorizontal();
847 AdjustChildren(aPresContext
, mChildInfosBefore
, isHorizontal
);
848 AdjustChildren(aPresContext
, mChildInfosAfter
, isHorizontal
);
851 static nsIFrame
* GetChildBoxForContent(nsIFrame
* aParentBox
,
852 nsIContent
* aContent
) {
853 // XXX Can this use GetPrimaryFrame?
854 for (nsIFrame
* f
: aParentBox
->PrincipalChildList()) {
855 if (f
->GetContent() == aContent
) {
862 void nsSplitterFrameInner::AdjustChildren(nsPresContext
* aPresContext
,
863 nsTArray
<nsSplitterInfo
>& aChildInfos
,
864 bool aIsHorizontal
) {
865 /// printf("------- AdjustChildren------\n");
867 for (auto& info
: aChildInfos
) {
868 nscoord newPref
= info
.pref
+ (info
.changed
- info
.current
);
869 if (nsIFrame
* childBox
=
870 GetChildBoxForContent(mParentBox
, info
.childElem
)) {
871 SetPreferredSize(childBox
, aIsHorizontal
, newPref
);
876 void nsSplitterFrameInner::SetPreferredSize(nsIFrame
* aChildBox
,
877 bool aIsHorizontal
, nscoord aSize
) {
879 aChildBox
->StyleMargin()->GetMargin(margin
);
881 aSize
-= (margin
.left
+ margin
.right
);
883 aSize
-= (margin
.top
+ margin
.bottom
);
886 RefPtr element
= nsStyledElement::FromNode(aChildBox
->GetContent());
891 // We set both the attribute and the CSS value, so that XUL persist="" keeps
892 // working, see bug 1790712.
894 int32_t pixels
= aSize
/ AppUnitsPerCSSPixel();
895 nsAutoString attrValue
;
896 attrValue
.AppendInt(pixels
);
897 element
->SetAttr(aIsHorizontal
? nsGkAtoms::width
: nsGkAtoms::height
,
898 attrValue
, IgnoreErrors());
900 nsCOMPtr
<nsICSSDeclaration
> decl
= element
->Style();
902 nsAutoCString cssValue
;
903 cssValue
.AppendInt(pixels
);
904 cssValue
.AppendLiteral("px");
905 decl
->SetProperty(aIsHorizontal
? "width"_ns
: "height"_ns
, cssValue
, ""_ns
,
909 void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff
,
910 nsTArray
<nsSplitterInfo
>& aChildInfos
,
911 int32_t& aSpaceLeft
) {
914 for (auto& info
: aChildInfos
) {
915 nscoord min
= info
.min
;
916 nscoord max
= info
.max
;
917 nscoord
& c
= info
.changed
;
919 // figure our how much space to add or remove
920 if (c
+ aDiff
< min
) {
923 } else if (c
+ aDiff
> max
) {
931 // there is not space left? We are done
941 * Ok if we want to resize a child we will know the actual size in pixels we
942 * want it to be. This is not the preferred size. But the only way we can change
943 * a child is by manipulating its preferred size. So give the actual pixel size
944 * this method will figure out the preferred size and set it.
947 void nsSplitterFrameInner::ResizeChildTo(nscoord
& aDiff
) {
948 nscoord spaceLeft
= 0;
950 if (!mChildInfosBefore
.IsEmpty()) {
951 AddRemoveSpace(aDiff
, mChildInfosBefore
, spaceLeft
);
952 // If there is any space left over remove it from the diff we were
957 AddRemoveSpace(-aDiff
, mChildInfosAfter
, spaceLeft
);
959 if (spaceLeft
!= 0 && !mChildInfosAfter
.IsEmpty()) {
961 AddRemoveSpace(spaceLeft
, mChildInfosBefore
, spaceLeft
);