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 "nsContentCID.h"
33 #include "nsLayoutUtils.h"
34 #include "nsDisplayList.h"
35 #include "nsContentUtils.h"
36 #include "nsFlexContainerFrame.h"
37 #include "mozilla/dom/Element.h"
38 #include "mozilla/dom/Event.h"
39 #include "mozilla/dom/MouseEvent.h"
40 #include "mozilla/MouseEvents.h"
41 #include "mozilla/PresShell.h"
42 #include "mozilla/UniquePtr.h"
43 #include "nsStyledElement.h"
45 using namespace mozilla
;
47 using mozilla::dom::Element
;
48 using mozilla::dom::Event
;
50 class nsSplitterInfo
{
57 nsCOMPtr
<nsIContent
> childElem
;
60 enum class ResizeType
{
61 // Resize the closest sibling in a given direction.
63 // Resize the farthest sibling in a given direction.
65 // Resize only flexible siblings in a given direction.
67 // No space should be taken out of any children in that direction.
68 // FIXME(emilio): This is a rather odd name...
70 // Only resize adjacent siblings.
72 // Don't resize anything in a given direction.
75 static ResizeType
ResizeTypeFromAttribute(const Element
& aElement
,
77 static Element::AttrValuesArray strings
[] = {
78 nsGkAtoms::farthest
, nsGkAtoms::flex
, nsGkAtoms::grow
,
79 nsGkAtoms::sibling
, nsGkAtoms::none
, nullptr};
80 switch (aElement
.FindAttrValueIn(kNameSpaceID_None
, aAttribute
, strings
,
83 return ResizeType::Farthest
;
85 return ResizeType::Flex
;
87 // Grow only applies to resizeAfter.
88 if (aAttribute
== nsGkAtoms::resizeafter
) {
89 return ResizeType::Grow
;
93 return ResizeType::Sibling
;
95 return ResizeType::None
;
99 return ResizeType::Closest
;
102 class nsSplitterFrameInner final
: public nsIDOMEventListener
{
104 virtual ~nsSplitterFrameInner();
108 NS_DECL_NSIDOMEVENTLISTENER
110 explicit nsSplitterFrameInner(nsSplitterFrame
* aSplitter
)
111 : mOuter(aSplitter
) {}
113 void Disconnect() { mOuter
= nullptr; }
115 nsresult
MouseDown(Event
* aMouseEvent
);
116 nsresult
MouseUp(Event
* aMouseEvent
);
117 nsresult
MouseMove(Event
* aMouseEvent
);
119 void MouseDrag(nsPresContext
* aPresContext
, WidgetGUIEvent
* aEvent
);
120 void MouseUp(nsPresContext
* aPresContext
, WidgetGUIEvent
* aEvent
);
122 void AdjustChildren(nsPresContext
* aPresContext
);
123 void AdjustChildren(nsPresContext
* aPresContext
,
124 nsTArray
<nsSplitterInfo
>& aChildInfos
,
127 void AddRemoveSpace(nscoord aDiff
, nsTArray
<nsSplitterInfo
>& aChildInfos
,
128 int32_t& aSpaceLeft
);
130 void ResizeChildTo(nscoord
& aDiff
);
135 void RemoveListener();
137 enum class State
{ Open
, CollapsedBefore
, CollapsedAfter
, Dragging
};
138 enum CollapseDirection
{ Before
, After
};
140 ResizeType
GetResizeBefore();
141 ResizeType
GetResizeAfter();
144 bool SupportsCollapseDirection(CollapseDirection aDirection
);
147 void SetPreferredSize(nsIFrame
* aChildBox
, bool aIsHorizontal
, nscoord aSize
);
149 nsSplitterFrame
* mOuter
;
150 bool mDidDrag
= false;
151 nscoord mDragStart
= 0;
152 nsIFrame
* mParentBox
= nullptr;
153 bool mPressed
= false;
154 nsTArray
<nsSplitterInfo
> mChildInfosBefore
;
155 nsTArray
<nsSplitterInfo
> mChildInfosAfter
;
156 State mState
= State::Open
;
157 nscoord mSplitterPos
= 0;
158 bool mDragging
= false;
160 const Element
* SplitterElement() const {
161 return mOuter
->GetContent()->AsElement();
165 NS_IMPL_ISUPPORTS(nsSplitterFrameInner
, nsIDOMEventListener
)
167 ResizeType
nsSplitterFrameInner::GetResizeBefore() {
168 return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizebefore
);
171 ResizeType
nsSplitterFrameInner::GetResizeAfter() {
172 return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizeafter
);
175 nsSplitterFrameInner::~nsSplitterFrameInner() = default;
177 nsSplitterFrameInner::State
nsSplitterFrameInner::GetState() {
178 static Element::AttrValuesArray strings
[] = {nsGkAtoms::dragging
,
179 nsGkAtoms::collapsed
, nullptr};
180 static Element::AttrValuesArray strings_substate
[] = {
181 nsGkAtoms::before
, nsGkAtoms::after
, nullptr};
182 switch (SplitterElement()->FindAttrValueIn(
183 kNameSpaceID_None
, nsGkAtoms::state
, strings
, eCaseMatters
)) {
185 return State::Dragging
;
187 switch (SplitterElement()->FindAttrValueIn(
188 kNameSpaceID_None
, nsGkAtoms::substate
, strings_substate
,
191 return State::CollapsedBefore
;
193 return State::CollapsedAfter
;
195 if (SupportsCollapseDirection(After
)) {
196 return State::CollapsedAfter
;
198 return State::CollapsedBefore
;
205 // NS_NewSplitterFrame
207 // Creates a new Toolbar frame and returns it
209 nsIFrame
* NS_NewSplitterFrame(PresShell
* aPresShell
, ComputedStyle
* aStyle
) {
210 return new (aPresShell
) nsSplitterFrame(aStyle
, aPresShell
->GetPresContext());
213 NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame
)
215 nsSplitterFrame::nsSplitterFrame(ComputedStyle
* aStyle
,
216 nsPresContext
* aPresContext
)
217 : SimpleXULLeafFrame(aStyle
, aPresContext
, kClassID
) {}
219 void nsSplitterFrame::Destroy(DestroyContext
& aContext
) {
221 mInner
->RemoveListener();
222 mInner
->Disconnect();
225 SimpleXULLeafFrame::Destroy(aContext
);
228 nsresult
nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID
,
232 SimpleXULLeafFrame::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 SimpleXULLeafFrame::Init(aContent
, aParent
, aPrevInFlow
);
251 mInner
->AddListener();
252 mInner
->mParentBox
= nullptr;
255 static bool IsValidParentBox(nsIFrame
* aFrame
) {
256 return aFrame
->IsFlexContainerFrame();
259 static nsIFrame
* GetValidParentBox(nsIFrame
* aChild
) {
260 return aChild
->GetParent() && IsValidParentBox(aChild
->GetParent())
261 ? aChild
->GetParent()
265 void nsSplitterFrame::Reflow(nsPresContext
* aPresContext
,
266 ReflowOutput
& aDesiredSize
,
267 const ReflowInput
& aReflowInput
,
268 nsReflowStatus
& aStatus
) {
269 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
270 mInner
->mParentBox
= GetValidParentBox(this);
271 mInner
->UpdateState();
273 return SimpleXULLeafFrame::Reflow(aPresContext
, aDesiredSize
, aReflowInput
,
277 static bool SplitterIsHorizontal(const nsIFrame
* aParentBox
) {
278 // If our parent is horizontal, the splitter is vertical and vice-versa.
279 MOZ_ASSERT(aParentBox
->IsFlexContainerFrame());
280 const FlexboxAxisInfo
info(aParentBox
);
281 return !info
.mIsRowOriented
;
285 nsSplitterFrame::HandlePress(nsPresContext
* aPresContext
,
286 WidgetGUIEvent
* aEvent
,
287 nsEventStatus
* aEventStatus
) {
292 nsSplitterFrame::HandleMultiplePress(nsPresContext
* aPresContext
,
293 WidgetGUIEvent
* aEvent
,
294 nsEventStatus
* aEventStatus
,
300 nsSplitterFrame::HandleDrag(nsPresContext
* aPresContext
, WidgetGUIEvent
* aEvent
,
301 nsEventStatus
* aEventStatus
) {
306 nsSplitterFrame::HandleRelease(nsPresContext
* aPresContext
,
307 WidgetGUIEvent
* aEvent
,
308 nsEventStatus
* aEventStatus
) {
312 void nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
313 const nsDisplayListSet
& aLists
) {
314 SimpleXULLeafFrame::BuildDisplayList(aBuilder
, aLists
);
316 // if the mouse is captured always return us as the frame.
317 if (mInner
->mDragging
&& aBuilder
->IsForEventDelivery()) {
318 // XXX It's probably better not to check visibility here, right?
319 aLists
.Outlines()->AppendNewToTop
<nsDisplayEventReceiver
>(aBuilder
, this);
324 nsresult
nsSplitterFrame::HandleEvent(nsPresContext
* aPresContext
,
325 WidgetGUIEvent
* aEvent
,
326 nsEventStatus
* aEventStatus
) {
327 NS_ENSURE_ARG_POINTER(aEventStatus
);
328 if (nsEventStatus_eConsumeNoDefault
== *aEventStatus
) {
332 AutoWeakFrame
weakFrame(this);
333 RefPtr
<nsSplitterFrameInner
> inner(mInner
);
334 switch (aEvent
->mMessage
) {
336 inner
->MouseDrag(aPresContext
, aEvent
);
340 if (aEvent
->AsMouseEvent()->mButton
== MouseButton::ePrimary
) {
341 inner
->MouseUp(aPresContext
, aEvent
);
349 NS_ENSURE_STATE(weakFrame
.IsAlive());
350 return SimpleXULLeafFrame::HandleEvent(aPresContext
, aEvent
, aEventStatus
);
353 void nsSplitterFrameInner::MouseUp(nsPresContext
* aPresContext
,
354 WidgetGUIEvent
* aEvent
) {
355 if (mDragging
&& mOuter
) {
356 AdjustChildren(aPresContext
);
358 PresShell::ReleaseCapturingContent(); // XXXndeakin is this needed?
360 State newState
= GetState();
361 // if the state is dragging then make it Open.
362 if (newState
== State::Dragging
) {
363 mOuter
->mContent
->AsElement()->SetAttr(kNameSpaceID_None
,
364 nsGkAtoms::state
, u
""_ns
, true);
369 // if we dragged then fire a command event.
371 RefPtr
<nsXULElement
> element
=
372 nsXULElement::FromNode(mOuter
->GetContent());
373 element
->DoCommand();
376 // printf("MouseUp\n");
379 mChildInfosBefore
.Clear();
380 mChildInfosAfter
.Clear();
383 void nsSplitterFrameInner::MouseDrag(nsPresContext
* aPresContext
,
384 WidgetGUIEvent
* aEvent
) {
385 if (!mDragging
|| !mOuter
) {
389 const bool isHorizontal
= !mOuter
->IsHorizontal();
390 nsPoint pt
= nsLayoutUtils::GetEventCoordinatesRelativeTo(
391 aEvent
, RelativeTo
{mParentBox
});
392 nscoord pos
= isHorizontal
? pt
.x
: pt
.y
;
394 // take our current position and subtract the start location,
395 // mDragStart is in parent-box relative coordinates already.
398 for (auto& info
: mChildInfosBefore
) {
399 info
.changed
= info
.current
;
402 for (auto& info
: mChildInfosAfter
) {
403 info
.changed
= info
.current
;
405 nscoord oldPos
= pos
;
409 State currentState
= GetState();
410 bool supportsBefore
= SupportsCollapseDirection(Before
);
411 bool supportsAfter
= SupportsCollapseDirection(After
);
414 mOuter
->StyleVisibility()->mDirection
== StyleDirection::Rtl
;
415 bool pastEnd
= oldPos
> 0 && oldPos
> pos
;
416 bool pastBegin
= oldPos
< 0 && oldPos
< pos
;
418 // Swap the boundary checks in RTL mode
419 std::swap(pastEnd
, pastBegin
);
421 const bool isCollapsedBefore
= pastBegin
&& supportsBefore
;
422 const bool isCollapsedAfter
= pastEnd
&& supportsAfter
;
424 // if we are in a collapsed position
425 if (isCollapsedBefore
|| isCollapsedAfter
) {
426 // and we are not collapsed then collapse
427 if (currentState
== State::Dragging
) {
429 // printf("Collapse right\n");
431 RefPtr
<Element
> outer
= mOuter
->mContent
->AsElement();
432 outer
->SetAttr(kNameSpaceID_None
, nsGkAtoms::substate
, u
"after"_ns
,
434 outer
->SetAttr(kNameSpaceID_None
, nsGkAtoms::state
, u
"collapsed"_ns
,
438 } else if (pastBegin
) {
439 // printf("Collapse left\n");
440 if (supportsBefore
) {
441 RefPtr
<Element
> outer
= mOuter
->mContent
->AsElement();
442 outer
->SetAttr(kNameSpaceID_None
, nsGkAtoms::substate
, u
"before"_ns
,
444 outer
->SetAttr(kNameSpaceID_None
, nsGkAtoms::state
, u
"collapsed"_ns
,
450 // if we are not in a collapsed position and we are not dragging make sure
452 if (currentState
!= State::Dragging
) {
453 mOuter
->mContent
->AsElement()->SetAttr(
454 kNameSpaceID_None
, nsGkAtoms::state
, u
"dragging"_ns
, true);
456 AdjustChildren(aPresContext
);
462 void nsSplitterFrameInner::AddListener() {
463 mOuter
->GetContent()->AddEventListener(u
"mouseup"_ns
, this, false, false);
464 mOuter
->GetContent()->AddEventListener(u
"mousedown"_ns
, this, false, false);
465 mOuter
->GetContent()->AddEventListener(u
"mousemove"_ns
, this, false, false);
466 mOuter
->GetContent()->AddEventListener(u
"mouseout"_ns
, this, false, false);
469 void nsSplitterFrameInner::RemoveListener() {
470 NS_ENSURE_TRUE_VOID(mOuter
);
471 mOuter
->GetContent()->RemoveEventListener(u
"mouseup"_ns
, this, false);
472 mOuter
->GetContent()->RemoveEventListener(u
"mousedown"_ns
, this, false);
473 mOuter
->GetContent()->RemoveEventListener(u
"mousemove"_ns
, this, false);
474 mOuter
->GetContent()->RemoveEventListener(u
"mouseout"_ns
, this, false);
477 nsresult
nsSplitterFrameInner::HandleEvent(dom::Event
* aEvent
) {
478 nsAutoString eventType
;
479 aEvent
->GetType(eventType
);
480 if (eventType
.EqualsLiteral("mouseup")) return MouseUp(aEvent
);
481 if (eventType
.EqualsLiteral("mousedown")) return MouseDown(aEvent
);
482 if (eventType
.EqualsLiteral("mousemove") ||
483 eventType
.EqualsLiteral("mouseout"))
484 return MouseMove(aEvent
);
486 MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
490 nsresult
nsSplitterFrameInner::MouseUp(Event
* aMouseEvent
) {
491 NS_ENSURE_TRUE(mOuter
, NS_OK
);
494 PresShell::ReleaseCapturingContent();
499 template <typename LengthLike
>
500 static nscoord
ToLengthWithFallback(const LengthLike
& aLengthLike
,
502 if (aLengthLike
.ConvertsToLength()) {
503 return aLengthLike
.ToLength();
508 template <typename LengthLike
>
509 static nsSize
ToLengthWithFallback(const LengthLike
& aWidth
,
510 const LengthLike
& aHeight
,
511 nscoord aFallback
= 0) {
512 return {ToLengthWithFallback(aWidth
, aFallback
),
513 ToLengthWithFallback(aHeight
, aFallback
)};
516 static void ApplyMargin(nsSize
& aSize
, const nsMargin
& aMargin
) {
517 if (aSize
.width
!= NS_UNCONSTRAINEDSIZE
) {
518 aSize
.width
+= aMargin
.LeftRight();
520 if (aSize
.height
!= NS_UNCONSTRAINEDSIZE
) {
521 aSize
.height
+= aMargin
.TopBottom();
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
);
550 const bool isHorizontal
= !mOuter
->IsHorizontal();
552 const nsIContent
* outerContent
= mOuter
->GetContent();
554 const ResizeType resizeBefore
= GetResizeBefore();
555 const ResizeType resizeAfter
= GetResizeAfter();
556 const int32_t childCount
= mParentBox
->PrincipalChildList().GetLength();
558 mChildInfosBefore
.Clear();
559 mChildInfosAfter
.Clear();
562 bool foundOuter
= false;
563 CSSOrderAwareFrameIterator
iter(
564 mParentBox
, FrameChildListID::Principal
,
565 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll
,
566 CSSOrderAwareFrameIterator::OrderState::Unknown
,
567 CSSOrderAwareFrameIterator::OrderingProperty::Order
);
568 for (; !iter
.AtEnd(); iter
.Next()) {
569 nsIFrame
* childBox
= iter
.get();
570 if (childBox
== mOuter
) {
573 // We're at the beginning, nothing to do.
576 if (count
== childCount
- 1 && resizeAfter
!= ResizeType::Grow
) {
577 // If it's the last index then we need to allow for resizeafter="grow"
583 nsIContent
* content
= childBox
->GetContent();
584 // XXX flex seems untested, as it uses mBoxFlex rather than actual flexbox
586 const nscoord flex
= childBox
->StyleXUL()->mBoxFlex
;
587 const bool isBefore
= !foundOuter
;
588 const bool isResizable
= [&] {
589 if (auto* element
= nsXULElement::FromNode(content
)) {
590 if (element
->NodeInfo()->NameAtom() == nsGkAtoms::splitter
) {
591 // skip over any splitters
595 // We need to check for hidden attribute too, since treecols with
596 // the hidden="true" attribute are not really hidden, just collapsed
597 if (element
->GetXULBoolAttr(nsGkAtoms::fixed
) ||
598 element
->GetXULBoolAttr(nsGkAtoms::hidden
)) {
603 // We need to check this here rather than in the switch before because we
604 // want `sibling` to work in the DOM order, not frame tree order.
605 if (resizeBefore
== ResizeType::Sibling
&&
606 content
->GetNextElementSibling() == outerContent
) {
609 if (resizeAfter
== ResizeType::Sibling
&&
610 content
->GetPreviousElementSibling() == outerContent
) {
614 const ResizeType resizeType
= isBefore
? resizeBefore
: resizeAfter
;
615 switch (resizeType
) {
616 case ResizeType::Grow
:
617 case ResizeType::None
:
618 case ResizeType::Sibling
:
620 case ResizeType::Flex
:
622 case ResizeType::Closest
:
623 case ResizeType::Farthest
:
633 nsSize curSize
= childBox
->GetSize();
634 const auto& pos
= *childBox
->StylePosition();
635 nsSize minSize
= ToLengthWithFallback(pos
.mMinWidth
, pos
.mMinHeight
);
636 nsSize maxSize
= ToLengthWithFallback(pos
.mMaxWidth
, pos
.mMaxHeight
,
637 NS_UNCONSTRAINEDSIZE
);
638 nsSize
prefSize(ToLengthWithFallback(pos
.mWidth
, curSize
.width
),
639 ToLengthWithFallback(pos
.mHeight
, curSize
.height
));
641 maxSize
.width
= std::max(maxSize
.width
, minSize
.width
);
642 maxSize
.height
= std::max(maxSize
.height
, minSize
.height
);
644 NS_CSS_MINMAX(prefSize
.width
, minSize
.width
, maxSize
.width
);
646 NS_CSS_MINMAX(prefSize
.height
, minSize
.height
, maxSize
.height
);
649 childBox
->StyleMargin()->GetMargin(m
);
651 ApplyMargin(curSize
, m
);
652 ApplyMargin(minSize
, m
);
653 ApplyMargin(maxSize
, m
);
654 ApplyMargin(prefSize
, m
);
656 auto& list
= isBefore
? mChildInfosBefore
: mChildInfosAfter
;
657 nsSplitterInfo
& info
= *list
.AppendElement();
658 info
.childElem
= content
;
659 info
.min
= isHorizontal
? minSize
.width
: minSize
.height
;
660 info
.max
= isHorizontal
? maxSize
.width
: maxSize
.height
;
661 info
.pref
= isHorizontal
? prefSize
.width
: prefSize
.height
;
662 info
.current
= info
.changed
= isHorizontal
? curSize
.width
: curSize
.height
;
671 const bool reverseDirection
= [&] {
672 MOZ_ASSERT(mParentBox
->IsFlexContainerFrame());
673 const FlexboxAxisInfo
info(mParentBox
);
674 if (!info
.mIsRowOriented
) {
675 return info
.mIsMainAxisReversed
;
678 mParentBox
->StyleVisibility()->mDirection
== StyleDirection::Rtl
;
679 return info
.mIsMainAxisReversed
!= rtl
;
682 if (reverseDirection
) {
683 // The before array is really the after array, and the order needs to be
684 // reversed. First reverse both arrays.
685 mChildInfosBefore
.Reverse();
686 mChildInfosAfter
.Reverse();
688 // Now swap the two arrays.
689 std::swap(mChildInfosBefore
, mChildInfosAfter
);
692 // if resizebefore is not Farthest, reverse the list because the first child
693 // in the list is the farthest, and we want the first child to be the closest.
694 if (resizeBefore
!= ResizeType::Farthest
) {
695 mChildInfosBefore
.Reverse();
698 // if the resizeafter is the Farthest we must reverse the list because the
699 // first child in the list is the closest we want the first child to be the
701 if (resizeAfter
== ResizeType::Farthest
) {
702 mChildInfosAfter
.Reverse();
707 nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent
, mParentBox
);
710 mSplitterPos
= mOuter
->mRect
.x
;
713 mSplitterPos
= mOuter
->mRect
.y
;
718 // printf("Pressed mDragStart=%d\n",mDragStart);
720 PresShell::SetCapturingContent(mOuter
->GetContent(),
721 CaptureFlags::IgnoreAllowedState
);
726 nsresult
nsSplitterFrameInner::MouseMove(Event
* aMouseEvent
) {
727 NS_ENSURE_TRUE(mOuter
, NS_OK
);
736 nsCOMPtr
<nsIDOMEventListener
> kungfuDeathGrip(this);
737 mOuter
->mContent
->AsElement()->SetAttr(kNameSpaceID_None
, nsGkAtoms::state
,
738 u
"dragging"_ns
, true);
746 bool nsSplitterFrameInner::SupportsCollapseDirection(
747 nsSplitterFrameInner::CollapseDirection aDirection
) {
748 static Element::AttrValuesArray strings
[] = {
749 nsGkAtoms::before
, nsGkAtoms::after
, nsGkAtoms::both
, nullptr};
751 switch (SplitterElement()->FindAttrValueIn(
752 kNameSpaceID_None
, nsGkAtoms::collapse
, strings
, eCaseMatters
)) {
754 return (aDirection
== Before
);
756 return (aDirection
== After
);
764 static nsIFrame
* SlowOrderAwareSibling(nsIFrame
* aBox
, bool aNext
) {
765 nsIFrame
* parent
= aBox
->GetParent();
769 CSSOrderAwareFrameIterator
iter(
770 parent
, FrameChildListID::Principal
,
771 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll
,
772 CSSOrderAwareFrameIterator::OrderState::Unknown
,
773 CSSOrderAwareFrameIterator::OrderingProperty::Order
);
775 nsIFrame
* prevSibling
= nullptr;
776 for (; !iter
.AtEnd(); iter
.Next()) {
777 nsIFrame
* current
= iter
.get();
778 if (!aNext
&& current
== aBox
) {
781 if (aNext
&& prevSibling
== aBox
) {
784 prevSibling
= current
;
789 void nsSplitterFrameInner::UpdateState() {
790 // State Transitions:
792 // Open -> CollapsedBefore
793 // Open -> CollapsedAfter
794 // CollapsedBefore -> Open
795 // CollapsedBefore -> Dragging
796 // CollapsedAfter -> Open
797 // CollapsedAfter -> Dragging
799 // Dragging -> CollapsedBefore (auto collapse)
800 // Dragging -> CollapsedAfter (auto collapse)
802 State newState
= GetState();
804 if (newState
== mState
) {
809 if ((SupportsCollapseDirection(Before
) || SupportsCollapseDirection(After
)) &&
810 IsValidParentBox(mOuter
->GetParent())) {
811 // Find the splitter's immediate sibling.
813 newState
== State::CollapsedBefore
|| mState
== State::CollapsedBefore
;
814 nsIFrame
* splitterSibling
= SlowOrderAwareSibling(mOuter
, !prev
);
815 if (splitterSibling
) {
816 nsCOMPtr
<nsIContent
> sibling
= splitterSibling
->GetContent();
817 if (sibling
&& sibling
->IsElement()) {
818 if (mState
== State::CollapsedBefore
||
819 mState
== State::CollapsedAfter
) {
820 // CollapsedBefore -> Open
821 // CollapsedBefore -> Dragging
822 // CollapsedAfter -> Open
823 // CollapsedAfter -> Dragging
824 nsContentUtils::AddScriptRunner(new nsUnsetAttrRunnable(
825 sibling
->AsElement(), nsGkAtoms::collapsed
));
826 } else if ((mState
== State::Open
|| mState
== State::Dragging
) &&
827 (newState
== State::CollapsedBefore
||
828 newState
== State::CollapsedAfter
)) {
829 // Open -> CollapsedBefore / CollapsedAfter
830 // Dragging -> CollapsedBefore / CollapsedAfter
831 nsContentUtils::AddScriptRunner(new nsSetAttrRunnable(
832 sibling
->AsElement(), nsGkAtoms::collapsed
, u
"true"_ns
));
840 void nsSplitterFrameInner::EnsureOrient() {
841 mOuter
->mIsHorizontal
= SplitterIsHorizontal(mParentBox
);
844 void nsSplitterFrameInner::AdjustChildren(nsPresContext
* aPresContext
) {
846 const bool isHorizontal
= !mOuter
->IsHorizontal();
848 AdjustChildren(aPresContext
, mChildInfosBefore
, isHorizontal
);
849 AdjustChildren(aPresContext
, mChildInfosAfter
, isHorizontal
);
852 static nsIFrame
* GetChildBoxForContent(nsIFrame
* aParentBox
,
853 nsIContent
* aContent
) {
854 // XXX Can this use GetPrimaryFrame?
855 for (nsIFrame
* f
: aParentBox
->PrincipalChildList()) {
856 if (f
->GetContent() == aContent
) {
863 void nsSplitterFrameInner::AdjustChildren(nsPresContext
* aPresContext
,
864 nsTArray
<nsSplitterInfo
>& aChildInfos
,
865 bool aIsHorizontal
) {
866 /// printf("------- AdjustChildren------\n");
868 for (auto& info
: aChildInfos
) {
869 nscoord newPref
= info
.pref
+ (info
.changed
- info
.current
);
870 if (nsIFrame
* childBox
=
871 GetChildBoxForContent(mParentBox
, info
.childElem
)) {
872 SetPreferredSize(childBox
, aIsHorizontal
, newPref
);
877 void nsSplitterFrameInner::SetPreferredSize(nsIFrame
* aChildBox
,
878 bool aIsHorizontal
, nscoord aSize
) {
880 aChildBox
->StyleMargin()->GetMargin(margin
);
882 aSize
-= (margin
.left
+ margin
.right
);
884 aSize
-= (margin
.top
+ margin
.bottom
);
887 RefPtr element
= nsStyledElement::FromNode(aChildBox
->GetContent());
892 // We set both the attribute and the CSS value, so that XUL persist="" keeps
893 // working, see bug 1790712.
895 int32_t pixels
= aSize
/ AppUnitsPerCSSPixel();
896 nsAutoString attrValue
;
897 attrValue
.AppendInt(pixels
);
898 element
->SetAttr(aIsHorizontal
? nsGkAtoms::width
: nsGkAtoms::height
,
899 attrValue
, IgnoreErrors());
901 nsCOMPtr
<nsICSSDeclaration
> decl
= element
->Style();
903 nsAutoCString cssValue
;
904 cssValue
.AppendInt(pixels
);
905 cssValue
.AppendLiteral("px");
906 decl
->SetProperty(aIsHorizontal
? "width"_ns
: "height"_ns
, cssValue
, ""_ns
,
910 void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff
,
911 nsTArray
<nsSplitterInfo
>& aChildInfos
,
912 int32_t& aSpaceLeft
) {
915 for (auto& info
: aChildInfos
) {
916 nscoord min
= info
.min
;
917 nscoord max
= info
.max
;
918 nscoord
& c
= info
.changed
;
920 // figure our how much space to add or remove
921 if (c
+ aDiff
< min
) {
924 } else if (c
+ aDiff
> max
) {
932 // there is not space left? We are done
942 * Ok if we want to resize a child we will know the actual size in pixels we
943 * want it to be. This is not the preferred size. But the only way we can change
944 * a child is by manipulating its preferred size. So give the actual pixel size
945 * this method will figure out the preferred size and set it.
948 void nsSplitterFrameInner::ResizeChildTo(nscoord
& aDiff
) {
949 nscoord spaceLeft
= 0;
951 if (!mChildInfosBefore
.IsEmpty()) {
952 AddRemoveSpace(aDiff
, mChildInfosBefore
, spaceLeft
);
953 // If there is any space left over remove it from the diff we were
958 AddRemoveSpace(-aDiff
, mChildInfosAfter
, spaceLeft
);
960 if (spaceLeft
!= 0 && !mChildInfosAfter
.IsEmpty()) {
962 AddRemoveSpace(spaceLeft
, mChildInfosBefore
, spaceLeft
);