Backed out 4 changesets (bug 1861985, bug 1860958, bug 1865364) for causing bustage...
[gecko.git] / layout / xul / nsSplitterFrame.cpp
blob89d3ac1c25e835ea1ad91606e8f6bdadb231f27f
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/. */
7 //
8 // Eric Vaughan
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 {
51 public:
52 nscoord min;
53 nscoord max;
54 nscoord current;
55 nscoord pref;
56 nscoord changed;
57 nsCOMPtr<nsIContent> childElem;
60 enum class ResizeType {
61 // Resize the closest sibling in a given direction.
62 Closest,
63 // Resize the farthest sibling in a given direction.
64 Farthest,
65 // Resize only flexible siblings in a given direction.
66 Flex,
67 // No space should be taken out of any children in that direction.
68 // FIXME(emilio): This is a rather odd name...
69 Grow,
70 // Only resize adjacent siblings.
71 Sibling,
72 // Don't resize anything in a given direction.
73 None,
75 static ResizeType ResizeTypeFromAttribute(const Element& aElement,
76 nsAtom* aAttribute) {
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,
81 eCaseMatters)) {
82 case 0:
83 return ResizeType::Farthest;
84 case 1:
85 return ResizeType::Flex;
86 case 2:
87 // Grow only applies to resizeAfter.
88 if (aAttribute == nsGkAtoms::resizeafter) {
89 return ResizeType::Grow;
91 break;
92 case 3:
93 return ResizeType::Sibling;
94 case 4:
95 return ResizeType::None;
96 default:
97 break;
99 return ResizeType::Closest;
102 class nsSplitterFrameInner final : public nsIDOMEventListener {
103 protected:
104 virtual ~nsSplitterFrameInner();
106 public:
107 NS_DECL_ISUPPORTS
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,
125 bool aIsHorizontal);
127 void AddRemoveSpace(nscoord aDiff, nsTArray<nsSplitterInfo>& aChildInfos,
128 int32_t& aSpaceLeft);
130 void ResizeChildTo(nscoord& aDiff);
132 void UpdateState();
134 void AddListener();
135 void RemoveListener();
137 enum class State { Open, CollapsedBefore, CollapsedAfter, Dragging };
138 enum CollapseDirection { Before, After };
140 ResizeType GetResizeBefore();
141 ResizeType GetResizeAfter();
142 State GetState();
144 bool SupportsCollapseDirection(CollapseDirection aDirection);
146 void EnsureOrient();
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)) {
184 case 0:
185 return State::Dragging;
186 case 1:
187 switch (SplitterElement()->FindAttrValueIn(
188 kNameSpaceID_None, nsGkAtoms::substate, strings_substate,
189 eCaseMatters)) {
190 case 0:
191 return State::CollapsedBefore;
192 case 1:
193 return State::CollapsedAfter;
194 default:
195 if (SupportsCollapseDirection(After)) {
196 return State::CollapsedAfter;
198 return State::CollapsedBefore;
201 return State::Open;
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) {
220 if (mInner) {
221 mInner->RemoveListener();
222 mInner->Disconnect();
223 mInner = nullptr;
225 SimpleXULLeafFrame::Destroy(aContext);
228 nsresult nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID,
229 nsAtom* aAttribute,
230 int32_t aModType) {
231 nsresult rv =
232 SimpleXULLeafFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
233 if (aAttribute == nsGkAtoms::state) {
234 mInner->UpdateState();
237 return rv;
241 * Initialize us. If we are in a box get our alignment so we know what direction
242 * we are
244 void nsSplitterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
245 nsIFrame* aPrevInFlow) {
246 MOZ_ASSERT(!mInner);
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()
262 : nullptr;
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,
274 aStatus);
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;
284 NS_IMETHODIMP
285 nsSplitterFrame::HandlePress(nsPresContext* aPresContext,
286 WidgetGUIEvent* aEvent,
287 nsEventStatus* aEventStatus) {
288 return NS_OK;
291 NS_IMETHODIMP
292 nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext,
293 WidgetGUIEvent* aEvent,
294 nsEventStatus* aEventStatus,
295 bool aControlHeld) {
296 return NS_OK;
299 NS_IMETHODIMP
300 nsSplitterFrame::HandleDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
301 nsEventStatus* aEventStatus) {
302 return NS_OK;
305 NS_IMETHODIMP
306 nsSplitterFrame::HandleRelease(nsPresContext* aPresContext,
307 WidgetGUIEvent* aEvent,
308 nsEventStatus* aEventStatus) {
309 return NS_OK;
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);
320 return;
324 nsresult nsSplitterFrame::HandleEvent(nsPresContext* aPresContext,
325 WidgetGUIEvent* aEvent,
326 nsEventStatus* aEventStatus) {
327 NS_ENSURE_ARG_POINTER(aEventStatus);
328 if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
329 return NS_OK;
332 AutoWeakFrame weakFrame(this);
333 RefPtr<nsSplitterFrameInner> inner(mInner);
334 switch (aEvent->mMessage) {
335 case eMouseMove:
336 inner->MouseDrag(aPresContext, aEvent);
337 break;
339 case eMouseUp:
340 if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) {
341 inner->MouseUp(aPresContext, aEvent);
343 break;
345 default:
346 break;
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);
357 AddListener();
358 PresShell::ReleaseCapturingContent(); // XXXndeakin is this needed?
359 mDragging = false;
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);
367 mPressed = false;
369 // if we dragged then fire a command event.
370 if (mDidDrag) {
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) {
386 return;
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.
396 pos -= mDragStart;
398 for (auto& info : mChildInfosBefore) {
399 info.changed = info.current;
402 for (auto& info : mChildInfosAfter) {
403 info.changed = info.current;
405 nscoord oldPos = pos;
407 ResizeChildTo(pos);
409 State currentState = GetState();
410 bool supportsBefore = SupportsCollapseDirection(Before);
411 bool supportsAfter = SupportsCollapseDirection(After);
413 const bool isRTL =
414 mOuter->StyleVisibility()->mDirection == StyleDirection::Rtl;
415 bool pastEnd = oldPos > 0 && oldPos > pos;
416 bool pastBegin = oldPos < 0 && oldPos < pos;
417 if (isRTL) {
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) {
428 if (pastEnd) {
429 // printf("Collapse right\n");
430 if (supportsAfter) {
431 RefPtr<Element> outer = mOuter->mContent->AsElement();
432 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"after"_ns,
433 true);
434 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
435 true);
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,
443 true);
444 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
445 true);
449 } else {
450 // if we are not in a collapsed position and we are not dragging make sure
451 // we are dragging.
452 if (currentState != State::Dragging) {
453 mOuter->mContent->AsElement()->SetAttr(
454 kNameSpaceID_None, nsGkAtoms::state, u"dragging"_ns, true);
456 AdjustChildren(aPresContext);
459 mDidDrag = true;
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");
487 return NS_OK;
490 nsresult nsSplitterFrameInner::MouseUp(Event* aMouseEvent) {
491 NS_ENSURE_TRUE(mOuter, NS_OK);
492 mPressed = false;
494 PresShell::ReleaseCapturingContent();
496 return NS_OK;
499 template <typename LengthLike>
500 static nscoord ToLengthWithFallback(const LengthLike& aLengthLike,
501 nscoord aFallback) {
502 if (aLengthLike.ConvertsToLength()) {
503 return aLengthLike.ToLength();
505 return aFallback;
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();
528 if (!mouseEvent) {
529 return NS_OK;
532 // only if left button
533 if (mouseEvent->Button() != 0) {
534 return NS_OK;
537 if (SplitterElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
538 nsGkAtoms::_true, eCaseMatters))
539 return NS_OK;
541 mParentBox = GetValidParentBox(mOuter);
542 if (!mParentBox) {
543 return NS_OK;
546 // get our index
547 mDidDrag = false;
549 EnsureOrient();
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();
560 int32_t count = 0;
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) {
571 foundOuter = true;
572 if (!count) {
573 // We're at the beginning, nothing to do.
574 return NS_OK;
576 if (count == childCount - 1 && resizeAfter != ResizeType::Grow) {
577 // If it's the last index then we need to allow for resizeafter="grow"
578 return NS_OK;
581 count++;
583 nsIContent* content = childBox->GetContent();
584 // XXX flex seems untested, as it uses mBoxFlex rather than actual flexbox
585 // flex.
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
592 return false;
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)) {
599 return false;
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) {
607 return true;
609 if (resizeAfter == ResizeType::Sibling &&
610 content->GetPreviousElementSibling() == outerContent) {
611 return true;
614 const ResizeType resizeType = isBefore ? resizeBefore : resizeAfter;
615 switch (resizeType) {
616 case ResizeType::Grow:
617 case ResizeType::None:
618 case ResizeType::Sibling:
619 return false;
620 case ResizeType::Flex:
621 return flex > 0;
622 case ResizeType::Closest:
623 case ResizeType::Farthest:
624 break;
626 return true;
627 }();
629 if (!isResizable) {
630 continue;
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);
643 prefSize.width =
644 NS_CSS_MINMAX(prefSize.width, minSize.width, maxSize.width);
645 prefSize.height =
646 NS_CSS_MINMAX(prefSize.height, minSize.height, maxSize.height);
648 nsMargin m;
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;
665 if (!foundOuter) {
666 return NS_OK;
669 mPressed = true;
671 const bool reverseDirection = [&] {
672 MOZ_ASSERT(mParentBox->IsFlexContainerFrame());
673 const FlexboxAxisInfo info(mParentBox);
674 if (!info.mIsRowOriented) {
675 return info.mIsMainAxisReversed;
677 const bool rtl =
678 mParentBox->StyleVisibility()->mDirection == StyleDirection::Rtl;
679 return info.mIsMainAxisReversed != rtl;
680 }();
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
700 // Farthest.
701 if (resizeAfter == ResizeType::Farthest) {
702 mChildInfosAfter.Reverse();
705 int32_t c;
706 nsPoint pt =
707 nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent, mParentBox);
708 if (isHorizontal) {
709 c = pt.x;
710 mSplitterPos = mOuter->mRect.x;
711 } else {
712 c = pt.y;
713 mSplitterPos = mOuter->mRect.y;
716 mDragStart = c;
718 // printf("Pressed mDragStart=%d\n",mDragStart);
720 PresShell::SetCapturingContent(mOuter->GetContent(),
721 CaptureFlags::IgnoreAllowedState);
723 return NS_OK;
726 nsresult nsSplitterFrameInner::MouseMove(Event* aMouseEvent) {
727 NS_ENSURE_TRUE(mOuter, NS_OK);
728 if (!mPressed) {
729 return NS_OK;
732 if (mDragging) {
733 return NS_OK;
736 nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this);
737 mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
738 u"dragging"_ns, true);
740 RemoveListener();
741 mDragging = true;
743 return NS_OK;
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)) {
753 case 0:
754 return (aDirection == Before);
755 case 1:
756 return (aDirection == After);
757 case 2:
758 return true;
761 return false;
764 static nsIFrame* SlowOrderAwareSibling(nsIFrame* aBox, bool aNext) {
765 nsIFrame* parent = aBox->GetParent();
766 if (!parent) {
767 return nullptr;
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) {
779 return prevSibling;
781 if (aNext && prevSibling == aBox) {
782 return current;
784 prevSibling = current;
786 return nullptr;
789 void nsSplitterFrameInner::UpdateState() {
790 // State Transitions:
791 // Open -> Dragging
792 // Open -> CollapsedBefore
793 // Open -> CollapsedAfter
794 // CollapsedBefore -> Open
795 // CollapsedBefore -> Dragging
796 // CollapsedAfter -> Open
797 // CollapsedAfter -> Dragging
798 // Dragging -> Open
799 // Dragging -> CollapsedBefore (auto collapse)
800 // Dragging -> CollapsedAfter (auto collapse)
802 State newState = GetState();
804 if (newState == mState) {
805 // No change.
806 return;
809 if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) &&
810 IsValidParentBox(mOuter->GetParent())) {
811 // Find the splitter's immediate sibling.
812 const bool prev =
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));
837 mState = newState;
840 void nsSplitterFrameInner::EnsureOrient() {
841 mOuter->mIsHorizontal = SplitterIsHorizontal(mParentBox);
844 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext) {
845 EnsureOrient();
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) {
857 return f;
860 return nullptr;
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) {
879 nsMargin margin;
880 aChildBox->StyleMargin()->GetMargin(margin);
881 if (aIsHorizontal) {
882 aSize -= (margin.left + margin.right);
883 } else {
884 aSize -= (margin.top + margin.bottom);
887 RefPtr element = nsStyledElement::FromNode(aChildBox->GetContent());
888 if (!element) {
889 return;
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,
907 IgnoreErrors());
910 void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff,
911 nsTArray<nsSplitterInfo>& aChildInfos,
912 int32_t& aSpaceLeft) {
913 aSpaceLeft = 0;
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) {
922 aDiff += (c - min);
923 c = min;
924 } else if (c + aDiff > max) {
925 aDiff -= (max - c);
926 c = max;
927 } else {
928 c += aDiff;
929 aDiff = 0;
932 // there is not space left? We are done
933 if (aDiff == 0) {
934 break;
938 aSpaceLeft = aDiff;
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
954 // originally given.
955 aDiff -= spaceLeft;
958 AddRemoveSpace(-aDiff, mChildInfosAfter, spaceLeft);
960 if (spaceLeft != 0 && !mChildInfosAfter.IsEmpty()) {
961 aDiff += spaceLeft;
962 AddRemoveSpace(spaceLeft, mChildInfosBefore, spaceLeft);