no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / layout / xul / nsSplitterFrame.cpp
blob47eadf8c286a8984d37cf0cc2b0d19b5e36efefe
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 "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 {
50 public:
51 nscoord min;
52 nscoord max;
53 nscoord current;
54 nscoord pref;
55 nscoord changed;
56 nsCOMPtr<nsIContent> childElem;
59 enum class ResizeType {
60 // Resize the closest sibling in a given direction.
61 Closest,
62 // Resize the farthest sibling in a given direction.
63 Farthest,
64 // Resize only flexible siblings in a given direction.
65 Flex,
66 // No space should be taken out of any children in that direction.
67 // FIXME(emilio): This is a rather odd name...
68 Grow,
69 // Only resize adjacent siblings.
70 Sibling,
71 // Don't resize anything in a given direction.
72 None,
74 static ResizeType ResizeTypeFromAttribute(const Element& aElement,
75 nsAtom* aAttribute) {
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,
80 eCaseMatters)) {
81 case 0:
82 return ResizeType::Farthest;
83 case 1:
84 return ResizeType::Flex;
85 case 2:
86 // Grow only applies to resizeAfter.
87 if (aAttribute == nsGkAtoms::resizeafter) {
88 return ResizeType::Grow;
90 break;
91 case 3:
92 return ResizeType::Sibling;
93 case 4:
94 return ResizeType::None;
95 default:
96 break;
98 return ResizeType::Closest;
101 class nsSplitterFrameInner final : public nsIDOMEventListener {
102 protected:
103 virtual ~nsSplitterFrameInner();
105 public:
106 NS_DECL_ISUPPORTS
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,
124 bool aIsHorizontal);
126 void AddRemoveSpace(nscoord aDiff, nsTArray<nsSplitterInfo>& aChildInfos,
127 int32_t& aSpaceLeft);
129 void ResizeChildTo(nscoord& aDiff);
131 void UpdateState();
133 void AddListener();
134 void RemoveListener();
136 enum class State { Open, CollapsedBefore, CollapsedAfter, Dragging };
137 enum CollapseDirection { Before, After };
139 ResizeType GetResizeBefore();
140 ResizeType GetResizeAfter();
141 State GetState();
143 bool SupportsCollapseDirection(CollapseDirection aDirection);
145 void EnsureOrient();
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)) {
183 case 0:
184 return State::Dragging;
185 case 1:
186 switch (SplitterElement()->FindAttrValueIn(
187 kNameSpaceID_None, nsGkAtoms::substate, strings_substate,
188 eCaseMatters)) {
189 case 0:
190 return State::CollapsedBefore;
191 case 1:
192 return State::CollapsedAfter;
193 default:
194 if (SupportsCollapseDirection(After)) {
195 return State::CollapsedAfter;
197 return State::CollapsedBefore;
200 return State::Open;
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) {
219 if (mInner) {
220 mInner->RemoveListener();
221 mInner->Disconnect();
222 mInner = nullptr;
224 SimpleXULLeafFrame::Destroy(aContext);
227 nsresult nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID,
228 nsAtom* aAttribute,
229 int32_t aModType) {
230 nsresult rv =
231 SimpleXULLeafFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
232 if (aAttribute == nsGkAtoms::state) {
233 mInner->UpdateState();
236 return rv;
240 * Initialize us. If we are in a box get our alignment so we know what direction
241 * we are
243 void nsSplitterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
244 nsIFrame* aPrevInFlow) {
245 MOZ_ASSERT(!mInner);
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()
261 : nullptr;
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,
273 aStatus);
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;
283 NS_IMETHODIMP
284 nsSplitterFrame::HandlePress(nsPresContext* aPresContext,
285 WidgetGUIEvent* aEvent,
286 nsEventStatus* aEventStatus) {
287 return NS_OK;
290 NS_IMETHODIMP
291 nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext,
292 WidgetGUIEvent* aEvent,
293 nsEventStatus* aEventStatus,
294 bool aControlHeld) {
295 return NS_OK;
298 NS_IMETHODIMP
299 nsSplitterFrame::HandleDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
300 nsEventStatus* aEventStatus) {
301 return NS_OK;
304 NS_IMETHODIMP
305 nsSplitterFrame::HandleRelease(nsPresContext* aPresContext,
306 WidgetGUIEvent* aEvent,
307 nsEventStatus* aEventStatus) {
308 return NS_OK;
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);
319 return;
323 nsresult nsSplitterFrame::HandleEvent(nsPresContext* aPresContext,
324 WidgetGUIEvent* aEvent,
325 nsEventStatus* aEventStatus) {
326 NS_ENSURE_ARG_POINTER(aEventStatus);
327 if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
328 return NS_OK;
331 AutoWeakFrame weakFrame(this);
332 RefPtr<nsSplitterFrameInner> inner(mInner);
333 switch (aEvent->mMessage) {
334 case eMouseMove:
335 inner->MouseDrag(aPresContext, aEvent);
336 break;
338 case eMouseUp:
339 if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) {
340 inner->MouseUp(aPresContext, aEvent);
342 break;
344 default:
345 break;
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);
356 AddListener();
357 PresShell::ReleaseCapturingContent(); // XXXndeakin is this needed?
358 mDragging = false;
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);
366 mPressed = false;
368 // if we dragged then fire a command event.
369 if (mDidDrag) {
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) {
385 return;
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.
395 pos -= mDragStart;
397 for (auto& info : mChildInfosBefore) {
398 info.changed = info.current;
401 for (auto& info : mChildInfosAfter) {
402 info.changed = info.current;
404 nscoord oldPos = pos;
406 ResizeChildTo(pos);
408 State currentState = GetState();
409 bool supportsBefore = SupportsCollapseDirection(Before);
410 bool supportsAfter = SupportsCollapseDirection(After);
412 const bool isRTL =
413 mOuter->StyleVisibility()->mDirection == StyleDirection::Rtl;
414 bool pastEnd = oldPos > 0 && oldPos > pos;
415 bool pastBegin = oldPos < 0 && oldPos < pos;
416 if (isRTL) {
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) {
427 if (pastEnd) {
428 // printf("Collapse right\n");
429 if (supportsAfter) {
430 RefPtr<Element> outer = mOuter->mContent->AsElement();
431 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"after"_ns,
432 true);
433 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
434 true);
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,
442 true);
443 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
444 true);
448 } else {
449 // if we are not in a collapsed position and we are not dragging make sure
450 // we are dragging.
451 if (currentState != State::Dragging) {
452 mOuter->mContent->AsElement()->SetAttr(
453 kNameSpaceID_None, nsGkAtoms::state, u"dragging"_ns, true);
455 AdjustChildren(aPresContext);
458 mDidDrag = true;
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");
486 return NS_OK;
489 nsresult nsSplitterFrameInner::MouseUp(Event* aMouseEvent) {
490 NS_ENSURE_TRUE(mOuter, NS_OK);
491 mPressed = false;
493 PresShell::ReleaseCapturingContent();
495 return NS_OK;
498 template <typename LengthLike>
499 static nscoord ToLengthWithFallback(const LengthLike& aLengthLike,
500 nscoord aFallback) {
501 if (aLengthLike.ConvertsToLength()) {
502 return aLengthLike.ToLength();
504 return aFallback;
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();
527 if (!mouseEvent) {
528 return NS_OK;
531 // only if left button
532 if (mouseEvent->Button() != 0) {
533 return NS_OK;
536 if (SplitterElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
537 nsGkAtoms::_true, eCaseMatters))
538 return NS_OK;
540 mParentBox = GetValidParentBox(mOuter);
541 if (!mParentBox) {
542 return NS_OK;
545 // get our index
546 mDidDrag = false;
548 EnsureOrient();
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();
559 int32_t count = 0;
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) {
570 foundOuter = true;
571 if (!count) {
572 // We're at the beginning, nothing to do.
573 return NS_OK;
575 if (count == childCount - 1 && resizeAfter != ResizeType::Grow) {
576 // If it's the last index then we need to allow for resizeafter="grow"
577 return NS_OK;
580 count++;
582 nsIContent* content = childBox->GetContent();
583 // XXX flex seems untested, as it uses mBoxFlex rather than actual flexbox
584 // flex.
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
591 return false;
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)) {
598 return false;
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) {
606 return true;
608 if (resizeAfter == ResizeType::Sibling &&
609 content->GetPreviousElementSibling() == outerContent) {
610 return true;
613 const ResizeType resizeType = isBefore ? resizeBefore : resizeAfter;
614 switch (resizeType) {
615 case ResizeType::Grow:
616 case ResizeType::None:
617 case ResizeType::Sibling:
618 return false;
619 case ResizeType::Flex:
620 return flex > 0;
621 case ResizeType::Closest:
622 case ResizeType::Farthest:
623 break;
625 return true;
626 }();
628 if (!isResizable) {
629 continue;
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);
642 prefSize.width =
643 NS_CSS_MINMAX(prefSize.width, minSize.width, maxSize.width);
644 prefSize.height =
645 NS_CSS_MINMAX(prefSize.height, minSize.height, maxSize.height);
647 nsMargin m;
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;
664 if (!foundOuter) {
665 return NS_OK;
668 mPressed = true;
670 const bool reverseDirection = [&] {
671 MOZ_ASSERT(mParentBox->IsFlexContainerFrame());
672 const FlexboxAxisInfo info(mParentBox);
673 if (!info.mIsRowOriented) {
674 return info.mIsMainAxisReversed;
676 const bool rtl =
677 mParentBox->StyleVisibility()->mDirection == StyleDirection::Rtl;
678 return info.mIsMainAxisReversed != rtl;
679 }();
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
699 // Farthest.
700 if (resizeAfter == ResizeType::Farthest) {
701 mChildInfosAfter.Reverse();
704 int32_t c;
705 nsPoint pt =
706 nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent, mParentBox);
707 if (isHorizontal) {
708 c = pt.x;
709 mSplitterPos = mOuter->mRect.x;
710 } else {
711 c = pt.y;
712 mSplitterPos = mOuter->mRect.y;
715 mDragStart = c;
717 // printf("Pressed mDragStart=%d\n",mDragStart);
719 PresShell::SetCapturingContent(mOuter->GetContent(),
720 CaptureFlags::IgnoreAllowedState);
722 return NS_OK;
725 nsresult nsSplitterFrameInner::MouseMove(Event* aMouseEvent) {
726 NS_ENSURE_TRUE(mOuter, NS_OK);
727 if (!mPressed) {
728 return NS_OK;
731 if (mDragging) {
732 return NS_OK;
735 nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this);
736 mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
737 u"dragging"_ns, true);
739 RemoveListener();
740 mDragging = true;
742 return NS_OK;
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)) {
752 case 0:
753 return (aDirection == Before);
754 case 1:
755 return (aDirection == After);
756 case 2:
757 return true;
760 return false;
763 static nsIFrame* SlowOrderAwareSibling(nsIFrame* aBox, bool aNext) {
764 nsIFrame* parent = aBox->GetParent();
765 if (!parent) {
766 return nullptr;
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) {
778 return prevSibling;
780 if (aNext && prevSibling == aBox) {
781 return current;
783 prevSibling = current;
785 return nullptr;
788 void nsSplitterFrameInner::UpdateState() {
789 // State Transitions:
790 // Open -> Dragging
791 // Open -> CollapsedBefore
792 // Open -> CollapsedAfter
793 // CollapsedBefore -> Open
794 // CollapsedBefore -> Dragging
795 // CollapsedAfter -> Open
796 // CollapsedAfter -> Dragging
797 // Dragging -> Open
798 // Dragging -> CollapsedBefore (auto collapse)
799 // Dragging -> CollapsedAfter (auto collapse)
801 State newState = GetState();
803 if (newState == mState) {
804 // No change.
805 return;
808 if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) &&
809 IsValidParentBox(mOuter->GetParent())) {
810 // Find the splitter's immediate sibling.
811 const bool prev =
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));
836 mState = newState;
839 void nsSplitterFrameInner::EnsureOrient() {
840 mOuter->mIsHorizontal = SplitterIsHorizontal(mParentBox);
843 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext) {
844 EnsureOrient();
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) {
856 return f;
859 return nullptr;
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) {
878 nsMargin margin;
879 aChildBox->StyleMargin()->GetMargin(margin);
880 if (aIsHorizontal) {
881 aSize -= (margin.left + margin.right);
882 } else {
883 aSize -= (margin.top + margin.bottom);
886 RefPtr element = nsStyledElement::FromNode(aChildBox->GetContent());
887 if (!element) {
888 return;
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,
906 IgnoreErrors());
909 void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff,
910 nsTArray<nsSplitterInfo>& aChildInfos,
911 int32_t& aSpaceLeft) {
912 aSpaceLeft = 0;
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) {
921 aDiff += (c - min);
922 c = min;
923 } else if (c + aDiff > max) {
924 aDiff -= (max - c);
925 c = max;
926 } else {
927 c += aDiff;
928 aDiff = 0;
931 // there is not space left? We are done
932 if (aDiff == 0) {
933 break;
937 aSpaceLeft = aDiff;
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
953 // originally given.
954 aDiff -= spaceLeft;
957 AddRemoveSpace(-aDiff, mChildInfosAfter, spaceLeft);
959 if (spaceLeft != 0 && !mChildInfosAfter.IsEmpty()) {
960 aDiff += spaceLeft;
961 AddRemoveSpace(spaceLeft, mChildInfosBefore, spaceLeft);