Bug 1793629 - Implement attention indicator for the unified extensions button, r...
[gecko.git] / layout / xul / nsSplitterFrame.cpp
blob6cf4a1d4536f3d8ac1df89a35b7650bc421c9967
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 "gfxContext.h"
15 #include "nsSplitterFrame.h"
16 #include "nsGkAtoms.h"
17 #include "nsXULElement.h"
18 #include "nsPresContext.h"
19 #include "mozilla/dom/Document.h"
20 #include "nsNameSpaceManager.h"
21 #include "nsScrollbarButtonFrame.h"
22 #include "nsIDOMEventListener.h"
23 #include "nsICSSDeclaration.h"
24 #include "nsFrameList.h"
25 #include "nsHTMLParts.h"
26 #include "mozilla/ComputedStyle.h"
27 #include "mozilla/CSSOrderAwareFrameIterator.h"
28 #include "nsBoxLayoutState.h"
29 #include "nsContainerFrame.h"
30 #include "nsContentCID.h"
31 #include "nsLayoutUtils.h"
32 #include "nsDisplayList.h"
33 #include "nsContentUtils.h"
34 #include "nsFlexContainerFrame.h"
35 #include "mozilla/dom/Element.h"
36 #include "mozilla/dom/Event.h"
37 #include "mozilla/dom/MouseEvent.h"
38 #include "mozilla/MouseEvents.h"
39 #include "mozilla/PresShell.h"
40 #include "mozilla/UniquePtr.h"
41 #include "nsStyledElement.h"
43 using namespace mozilla;
45 using mozilla::dom::Element;
46 using mozilla::dom::Event;
48 class nsSplitterInfo {
49 public:
50 nscoord min;
51 nscoord max;
52 nscoord current;
53 nscoord pref;
54 nscoord changed;
55 nsCOMPtr<nsIContent> childElem;
58 enum class ResizeType {
59 // Resize the closest sibling in a given direction.
60 Closest,
61 // Resize the farthest sibling in a given direction.
62 Farthest,
63 // Resize only flexible siblings in a given direction.
64 Flex,
65 // No space should be taken out of any children in that direction.
66 // FIXME(emilio): This is a rather odd name...
67 Grow,
68 // Only resize adjacent siblings.
69 Sibling,
70 // Don't resize anything in a given direction.
71 None,
73 static ResizeType ResizeTypeFromAttribute(const Element& aElement,
74 nsAtom* aAttribute) {
75 static Element::AttrValuesArray strings[] = {
76 nsGkAtoms::farthest, nsGkAtoms::flex, nsGkAtoms::grow,
77 nsGkAtoms::sibling, nsGkAtoms::none, nullptr};
78 switch (aElement.FindAttrValueIn(kNameSpaceID_None, aAttribute, strings,
79 eCaseMatters)) {
80 case 0:
81 return ResizeType::Farthest;
82 case 1:
83 return ResizeType::Flex;
84 case 2:
85 // Grow only applies to resizeAfter.
86 if (aAttribute == nsGkAtoms::resizeafter) {
87 return ResizeType::Grow;
89 break;
90 case 3:
91 return ResizeType::Sibling;
92 case 4:
93 return ResizeType::None;
94 default:
95 break;
97 return ResizeType::Closest;
100 class nsSplitterFrameInner final : public nsIDOMEventListener {
101 protected:
102 virtual ~nsSplitterFrameInner();
104 public:
105 NS_DECL_ISUPPORTS
106 NS_DECL_NSIDOMEVENTLISTENER
108 explicit nsSplitterFrameInner(nsSplitterFrame* aSplitter)
109 : mOuter(aSplitter) {}
111 void Disconnect() { mOuter = nullptr; }
113 nsresult MouseDown(Event* aMouseEvent);
114 nsresult MouseUp(Event* aMouseEvent);
115 nsresult MouseMove(Event* aMouseEvent);
117 void MouseDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
118 void MouseUp(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
120 void AdjustChildren(nsPresContext* aPresContext);
121 void AdjustChildren(nsPresContext* aPresContext,
122 nsTArray<nsSplitterInfo>& aChildInfos,
123 bool aIsHorizontal);
125 void AddRemoveSpace(nscoord aDiff, nsTArray<nsSplitterInfo>& aChildInfos,
126 int32_t& aSpaceLeft);
128 void ResizeChildTo(nscoord& aDiff);
130 void UpdateState();
132 void AddListener();
133 void RemoveListener();
135 enum class State { Open, CollapsedBefore, CollapsedAfter, Dragging };
136 enum CollapseDirection { Before, After };
138 ResizeType GetResizeBefore();
139 ResizeType GetResizeAfter();
140 State GetState();
142 bool SupportsCollapseDirection(CollapseDirection aDirection);
144 void EnsureOrient();
145 void SetPreferredSize(nsBoxLayoutState& aState, nsIFrame* aChildBox,
146 bool aIsHorizontal, nscoord aSize);
148 nsSplitterFrame* mOuter;
149 bool mDidDrag = false;
150 nscoord mDragStart = 0;
151 nsIFrame* mParentBox = nullptr;
152 bool mPressed = false;
153 nsTArray<nsSplitterInfo> mChildInfosBefore;
154 nsTArray<nsSplitterInfo> mChildInfosAfter;
155 State mState = State::Open;
156 nscoord mSplitterPos = 0;
157 bool mDragging = false;
159 const Element* SplitterElement() const {
160 return mOuter->GetContent()->AsElement();
164 NS_IMPL_ISUPPORTS(nsSplitterFrameInner, nsIDOMEventListener)
166 ResizeType nsSplitterFrameInner::GetResizeBefore() {
167 return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizebefore);
170 ResizeType nsSplitterFrameInner::GetResizeAfter() {
171 return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizeafter);
174 nsSplitterFrameInner::~nsSplitterFrameInner() = default;
176 nsSplitterFrameInner::State nsSplitterFrameInner::GetState() {
177 static Element::AttrValuesArray strings[] = {nsGkAtoms::dragging,
178 nsGkAtoms::collapsed, nullptr};
179 static Element::AttrValuesArray strings_substate[] = {
180 nsGkAtoms::before, nsGkAtoms::after, nullptr};
181 switch (SplitterElement()->FindAttrValueIn(
182 kNameSpaceID_None, nsGkAtoms::state, strings, eCaseMatters)) {
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 : nsBoxFrame(aStyle, aPresContext, kClassID) {}
218 void nsSplitterFrame::DestroyFrom(nsIFrame* aDestructRoot,
219 PostDestroyData& aPostDestroyData) {
220 if (mInner) {
221 mInner->RemoveListener();
222 mInner->Disconnect();
223 mInner = nullptr;
225 nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
228 nsresult nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID,
229 nsAtom* aAttribute,
230 int32_t aModType) {
231 nsresult rv =
232 nsBoxFrame::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 nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
251 mInner->AddListener();
252 mInner->mParentBox = nullptr;
255 static bool IsValidParentBox(nsIFrame* aFrame) {
256 return aFrame->IsXULBoxFrame() || aFrame->IsFlexContainerFrame();
259 static nsIFrame* GetValidParentBox(nsIFrame* aChild) {
260 return aChild->GetParent() && IsValidParentBox(aChild->GetParent())
261 ? aChild->GetParent()
262 : nullptr;
265 NS_IMETHODIMP
266 nsSplitterFrame::DoXULLayout(nsBoxLayoutState& aState) {
267 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
268 mInner->mParentBox = GetValidParentBox(this);
269 mInner->UpdateState();
272 return nsBoxFrame::DoXULLayout(aState);
275 static bool SplitterIsHorizontal(const nsIFrame* aParentBox) {
276 // If our parent is horizontal, the splitter is vertical and vice-versa.
277 if (aParentBox->IsXULBoxFrame()) {
278 return !aParentBox->HasAnyStateBits(NS_STATE_IS_HORIZONTAL);
280 MOZ_ASSERT(aParentBox->IsFlexContainerFrame());
281 const FlexboxAxisInfo info(aParentBox);
282 return !info.mIsRowOriented;
285 void nsSplitterFrame::GetInitialOrientation(bool& aIsHorizontal) {
286 if (nsIFrame* parent = GetValidParentBox(this)) {
287 aIsHorizontal = SplitterIsHorizontal(parent);
288 } else {
289 nsBoxFrame::GetInitialOrientation(aIsHorizontal);
293 NS_IMETHODIMP
294 nsSplitterFrame::HandlePress(nsPresContext* aPresContext,
295 WidgetGUIEvent* aEvent,
296 nsEventStatus* aEventStatus) {
297 return NS_OK;
300 NS_IMETHODIMP
301 nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext,
302 WidgetGUIEvent* aEvent,
303 nsEventStatus* aEventStatus,
304 bool aControlHeld) {
305 return NS_OK;
308 NS_IMETHODIMP
309 nsSplitterFrame::HandleDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
310 nsEventStatus* aEventStatus) {
311 return NS_OK;
314 NS_IMETHODIMP
315 nsSplitterFrame::HandleRelease(nsPresContext* aPresContext,
316 WidgetGUIEvent* aEvent,
317 nsEventStatus* aEventStatus) {
318 return NS_OK;
321 void nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
322 const nsDisplayListSet& aLists) {
323 nsBoxFrame::BuildDisplayList(aBuilder, aLists);
325 // if the mouse is captured always return us as the frame.
326 if (mInner->mDragging && aBuilder->IsForEventDelivery()) {
327 // XXX It's probably better not to check visibility here, right?
328 aLists.Outlines()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder, this);
329 return;
333 nsresult nsSplitterFrame::HandleEvent(nsPresContext* aPresContext,
334 WidgetGUIEvent* aEvent,
335 nsEventStatus* aEventStatus) {
336 NS_ENSURE_ARG_POINTER(aEventStatus);
337 if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
338 return NS_OK;
341 AutoWeakFrame weakFrame(this);
342 RefPtr<nsSplitterFrameInner> inner(mInner);
343 switch (aEvent->mMessage) {
344 case eMouseMove:
345 inner->MouseDrag(aPresContext, aEvent);
346 break;
348 case eMouseUp:
349 if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) {
350 inner->MouseUp(aPresContext, aEvent);
352 break;
354 default:
355 break;
358 NS_ENSURE_STATE(weakFrame.IsAlive());
359 return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
362 void nsSplitterFrameInner::MouseUp(nsPresContext* aPresContext,
363 WidgetGUIEvent* aEvent) {
364 if (mDragging && mOuter) {
365 AdjustChildren(aPresContext);
366 AddListener();
367 PresShell::ReleaseCapturingContent(); // XXXndeakin is this needed?
368 mDragging = false;
369 State newState = GetState();
370 // if the state is dragging then make it Open.
371 if (newState == State::Dragging) {
372 mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None,
373 nsGkAtoms::state, u""_ns, true);
376 mPressed = false;
378 // if we dragged then fire a command event.
379 if (mDidDrag) {
380 RefPtr<nsXULElement> element =
381 nsXULElement::FromNode(mOuter->GetContent());
382 element->DoCommand();
385 // printf("MouseUp\n");
388 mChildInfosBefore.Clear();
389 mChildInfosAfter.Clear();
392 void nsSplitterFrameInner::MouseDrag(nsPresContext* aPresContext,
393 WidgetGUIEvent* aEvent) {
394 if (!mDragging || !mOuter) {
395 return;
398 const bool isHorizontal = !mOuter->IsXULHorizontal();
399 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
400 aEvent, RelativeTo{mParentBox});
401 nscoord pos = isHorizontal ? pt.x : pt.y;
403 // take our current position and subtract the start location,
404 // mDragStart is in parent-box relative coordinates already.
405 pos -= mDragStart;
407 for (auto& info : mChildInfosBefore) {
408 info.changed = info.current;
411 for (auto& info : mChildInfosAfter) {
412 info.changed = info.current;
414 nscoord oldPos = pos;
416 ResizeChildTo(pos);
418 State currentState = GetState();
419 bool supportsBefore = SupportsCollapseDirection(Before);
420 bool supportsAfter = SupportsCollapseDirection(After);
422 const bool isRTL =
423 mOuter->StyleVisibility()->mDirection == StyleDirection::Rtl;
424 bool pastEnd = oldPos > 0 && oldPos > pos;
425 bool pastBegin = oldPos < 0 && oldPos < pos;
426 if (isRTL) {
427 // Swap the boundary checks in RTL mode
428 std::swap(pastEnd, pastBegin);
430 const bool isCollapsedBefore = pastBegin && supportsBefore;
431 const bool isCollapsedAfter = pastEnd && supportsAfter;
433 // if we are in a collapsed position
434 if (isCollapsedBefore || isCollapsedAfter) {
435 // and we are not collapsed then collapse
436 if (currentState == State::Dragging) {
437 if (pastEnd) {
438 // printf("Collapse right\n");
439 if (supportsAfter) {
440 RefPtr<Element> outer = mOuter->mContent->AsElement();
441 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"after"_ns,
442 true);
443 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
444 true);
447 } else if (pastBegin) {
448 // printf("Collapse left\n");
449 if (supportsBefore) {
450 RefPtr<Element> outer = mOuter->mContent->AsElement();
451 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"before"_ns,
452 true);
453 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
454 true);
458 } else {
459 // if we are not in a collapsed position and we are not dragging make sure
460 // we are dragging.
461 if (currentState != State::Dragging) {
462 mOuter->mContent->AsElement()->SetAttr(
463 kNameSpaceID_None, nsGkAtoms::state, u"dragging"_ns, true);
465 AdjustChildren(aPresContext);
468 mDidDrag = true;
471 void nsSplitterFrameInner::AddListener() {
472 mOuter->GetContent()->AddEventListener(u"mouseup"_ns, this, false, false);
473 mOuter->GetContent()->AddEventListener(u"mousedown"_ns, this, false, false);
474 mOuter->GetContent()->AddEventListener(u"mousemove"_ns, this, false, false);
475 mOuter->GetContent()->AddEventListener(u"mouseout"_ns, this, false, false);
478 void nsSplitterFrameInner::RemoveListener() {
479 NS_ENSURE_TRUE_VOID(mOuter);
480 mOuter->GetContent()->RemoveEventListener(u"mouseup"_ns, this, false);
481 mOuter->GetContent()->RemoveEventListener(u"mousedown"_ns, this, false);
482 mOuter->GetContent()->RemoveEventListener(u"mousemove"_ns, this, false);
483 mOuter->GetContent()->RemoveEventListener(u"mouseout"_ns, this, false);
486 nsresult nsSplitterFrameInner::HandleEvent(dom::Event* aEvent) {
487 nsAutoString eventType;
488 aEvent->GetType(eventType);
489 if (eventType.EqualsLiteral("mouseup")) return MouseUp(aEvent);
490 if (eventType.EqualsLiteral("mousedown")) return MouseDown(aEvent);
491 if (eventType.EqualsLiteral("mousemove") ||
492 eventType.EqualsLiteral("mouseout"))
493 return MouseMove(aEvent);
495 MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
496 return NS_OK;
499 nsresult nsSplitterFrameInner::MouseUp(Event* aMouseEvent) {
500 NS_ENSURE_TRUE(mOuter, NS_OK);
501 mPressed = false;
503 PresShell::ReleaseCapturingContent();
505 return NS_OK;
508 template <typename LengthLike>
509 static nscoord ToLengthWithFallback(const LengthLike& aLengthLike,
510 nscoord aFallback) {
511 if (aLengthLike.ConvertsToLength()) {
512 return aLengthLike.ToLength();
514 return aFallback;
517 template <typename LengthLike>
518 static nsSize ToLengthWithFallback(const LengthLike& aWidth,
519 const LengthLike& aHeight,
520 nscoord aFallback = 0) {
521 return {ToLengthWithFallback(aWidth, aFallback),
522 ToLengthWithFallback(aHeight, aFallback)};
525 nsresult nsSplitterFrameInner::MouseDown(Event* aMouseEvent) {
526 NS_ENSURE_TRUE(mOuter, NS_OK);
527 dom::MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
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 nsPresContext* outerPresContext = mOuter->PresContext();
549 RefPtr<gfxContext> rc =
550 outerPresContext->PresShell()->CreateReferenceRenderingContext();
551 nsBoxLayoutState state(outerPresContext, rc);
553 mDidDrag = false;
555 EnsureOrient();
556 const bool isHorizontal = !mOuter->IsXULHorizontal();
558 const nsIContent* outerContent = mOuter->GetContent();
560 const ResizeType resizeBefore = GetResizeBefore();
561 const ResizeType resizeAfter = GetResizeAfter();
562 const int32_t childCount = mParentBox->PrincipalChildList().GetLength();
564 mChildInfosBefore.Clear();
565 mChildInfosAfter.Clear();
566 int32_t count = 0;
568 bool foundOuter = false;
569 CSSOrderAwareFrameIterator iter(
570 mParentBox, layout::kPrincipalList,
571 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
572 CSSOrderAwareFrameIterator::OrderState::Unknown,
573 CSSOrderAwareFrameIterator::OrderingProperty::BoxOrdinalGroup);
574 for (; !iter.AtEnd(); iter.Next()) {
575 nsIFrame* childBox = iter.get();
576 if (childBox == mOuter) {
577 foundOuter = true;
578 if (!count) {
579 // We're at the beginning, nothing to do.
580 return NS_OK;
582 if (count == childCount - 1 && resizeAfter != ResizeType::Grow) {
583 // If it's the last index then we need to allow for resizeafter="grow"
584 return NS_OK;
587 count++;
589 nsIContent* content = childBox->GetContent();
590 const nscoord flex = childBox->GetXULFlex();
591 const bool isBefore = !foundOuter;
592 const bool isResizable = [&] {
593 if (auto* element = nsXULElement::FromNode(content)) {
594 if (element->NodeInfo()->NameAtom() == nsGkAtoms::splitter) {
595 // skip over any splitters
596 return false;
599 // We need to check for hidden attribute too, since treecols with
600 // the hidden="true" attribute are not really hidden, just collapsed
601 if (element->GetXULBoolAttr(nsGkAtoms::fixed) ||
602 element->GetXULBoolAttr(nsGkAtoms::hidden)) {
603 return false;
607 // We need to check this here rather than in the switch before because we
608 // want `sibling` to work in the DOM order, not frame tree order.
609 if (resizeBefore == ResizeType::Sibling &&
610 content->GetNextElementSibling() == outerContent) {
611 return true;
613 if (resizeAfter == ResizeType::Sibling &&
614 content->GetPreviousElementSibling() == outerContent) {
615 return true;
618 const ResizeType resizeType = isBefore ? resizeBefore : resizeAfter;
619 switch (resizeType) {
620 case ResizeType::Grow:
621 case ResizeType::None:
622 case ResizeType::Sibling:
623 return false;
624 case ResizeType::Flex:
625 return flex > 0;
626 case ResizeType::Closest:
627 case ResizeType::Farthest:
628 break;
630 return true;
631 }();
633 if (!isResizable) {
634 continue;
637 nsSize minSize;
638 nsSize prefSize;
639 nsSize maxSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
640 nsSize curSize = childBox->GetSize();
641 if (childBox->IsXULBoxFrame()) {
642 minSize = childBox->GetXULMinSize(state);
643 maxSize = childBox->GetXULMaxSize(state);
644 prefSize = childBox->GetXULPrefSize(state);
645 } else {
646 const auto& pos = *childBox->StylePosition();
647 minSize = ToLengthWithFallback(pos.mMinWidth, pos.mMinHeight);
648 maxSize = ToLengthWithFallback(pos.mMaxWidth, pos.mMaxHeight,
649 NS_UNCONSTRAINEDSIZE);
650 prefSize.width = ToLengthWithFallback(pos.mWidth, curSize.width);
651 prefSize.height = ToLengthWithFallback(pos.mHeight, curSize.height);
654 maxSize = nsIFrame::XULBoundsCheckMinMax(minSize, maxSize);
655 prefSize = nsIFrame::XULBoundsCheck(minSize, prefSize, maxSize);
657 nsSplitterFrame::AddXULMargin(childBox, minSize);
658 nsSplitterFrame::AddXULMargin(childBox, maxSize);
659 nsSplitterFrame::AddXULMargin(childBox, prefSize);
660 nsSplitterFrame::AddXULMargin(childBox, curSize);
662 auto& list = isBefore ? mChildInfosBefore : mChildInfosAfter;
663 nsSplitterInfo& info = *list.AppendElement();
664 info.childElem = content;
665 info.min = isHorizontal ? minSize.width : minSize.height;
666 info.max = isHorizontal ? maxSize.width : maxSize.height;
667 info.pref = isHorizontal ? prefSize.width : prefSize.height;
668 info.current = info.changed = isHorizontal ? curSize.width : curSize.height;
671 if (!foundOuter) {
672 return NS_OK;
675 mPressed = true;
677 const bool reverseDirection = [&] {
678 if (mParentBox->IsXULBoxFrame()) {
679 return !mParentBox->IsXULNormalDirection();
681 MOZ_ASSERT(mParentBox->IsFlexContainerFrame());
682 const FlexboxAxisInfo info(mParentBox);
683 if (!info.mIsRowOriented) {
684 return info.mIsMainAxisReversed;
686 const bool rtl =
687 mParentBox->StyleVisibility()->mDirection == StyleDirection::Rtl;
688 return info.mIsMainAxisReversed != rtl;
689 }();
691 if (reverseDirection) {
692 // The before array is really the after array, and the order needs to be
693 // reversed. First reverse both arrays.
694 mChildInfosBefore.Reverse();
695 mChildInfosAfter.Reverse();
697 // Now swap the two arrays.
698 std::swap(mChildInfosBefore, mChildInfosAfter);
701 // if resizebefore is not Farthest, reverse the list because the first child
702 // in the list is the farthest, and we want the first child to be the closest.
703 if (resizeBefore != ResizeType::Farthest) {
704 mChildInfosBefore.Reverse();
707 // if the resizeafter is the Farthest we must reverse the list because the
708 // first child in the list is the closest we want the first child to be the
709 // Farthest.
710 if (resizeAfter == ResizeType::Farthest) {
711 mChildInfosAfter.Reverse();
714 int32_t c;
715 nsPoint pt =
716 nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent, mParentBox);
717 if (isHorizontal) {
718 c = pt.x;
719 mSplitterPos = mOuter->mRect.x;
720 } else {
721 c = pt.y;
722 mSplitterPos = mOuter->mRect.y;
725 mDragStart = c;
727 // printf("Pressed mDragStart=%d\n",mDragStart);
729 PresShell::SetCapturingContent(mOuter->GetContent(),
730 CaptureFlags::IgnoreAllowedState);
732 return NS_OK;
735 nsresult nsSplitterFrameInner::MouseMove(Event* aMouseEvent) {
736 NS_ENSURE_TRUE(mOuter, NS_OK);
737 if (!mPressed) {
738 return NS_OK;
741 if (mDragging) {
742 return NS_OK;
745 nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this);
746 mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
747 u"dragging"_ns, true);
749 RemoveListener();
750 mDragging = true;
752 return NS_OK;
755 bool nsSplitterFrameInner::SupportsCollapseDirection(
756 nsSplitterFrameInner::CollapseDirection aDirection) {
757 static Element::AttrValuesArray strings[] = {
758 nsGkAtoms::before, nsGkAtoms::after, nsGkAtoms::both, nullptr};
760 switch (SplitterElement()->FindAttrValueIn(
761 kNameSpaceID_None, nsGkAtoms::collapse, strings, eCaseMatters)) {
762 case 0:
763 return (aDirection == Before);
764 case 1:
765 return (aDirection == After);
766 case 2:
767 return true;
770 return false;
773 void nsSplitterFrameInner::UpdateState() {
774 // State Transitions:
775 // Open -> Dragging
776 // Open -> CollapsedBefore
777 // Open -> CollapsedAfter
778 // CollapsedBefore -> Open
779 // CollapsedBefore -> Dragging
780 // CollapsedAfter -> Open
781 // CollapsedAfter -> Dragging
782 // Dragging -> Open
783 // Dragging -> CollapsedBefore (auto collapse)
784 // Dragging -> CollapsedAfter (auto collapse)
786 State newState = GetState();
788 if (newState == mState) {
789 // No change.
790 return;
793 if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) &&
794 IsValidParentBox(mOuter->GetParent())) {
795 // Find the splitter's immediate sibling.
796 const bool prev =
797 newState == State::CollapsedBefore || mState == State::CollapsedBefore;
798 nsIFrame* splitterSibling =
799 nsBoxFrame::SlowOrdinalGroupAwareSibling(mOuter, !prev);
800 if (splitterSibling) {
801 nsCOMPtr<nsIContent> sibling = splitterSibling->GetContent();
802 if (sibling && sibling->IsElement()) {
803 if (mState == State::CollapsedBefore ||
804 mState == State::CollapsedAfter) {
805 // CollapsedBefore -> Open
806 // CollapsedBefore -> Dragging
807 // CollapsedAfter -> Open
808 // CollapsedAfter -> Dragging
809 nsContentUtils::AddScriptRunner(new nsUnsetAttrRunnable(
810 sibling->AsElement(), nsGkAtoms::collapsed));
811 } else if ((mState == State::Open || mState == State::Dragging) &&
812 (newState == State::CollapsedBefore ||
813 newState == State::CollapsedAfter)) {
814 // Open -> CollapsedBefore / CollapsedAfter
815 // Dragging -> CollapsedBefore / CollapsedAfter
816 nsContentUtils::AddScriptRunner(new nsSetAttrRunnable(
817 sibling->AsElement(), nsGkAtoms::collapsed, u"true"_ns));
822 mState = newState;
825 void nsSplitterFrameInner::EnsureOrient() {
826 if (SplitterIsHorizontal(mParentBox))
827 mOuter->AddStateBits(NS_STATE_IS_HORIZONTAL);
828 else
829 mOuter->RemoveStateBits(NS_STATE_IS_HORIZONTAL);
832 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext) {
833 EnsureOrient();
834 const bool isHorizontal = !mOuter->IsXULHorizontal();
836 AdjustChildren(aPresContext, mChildInfosBefore, isHorizontal);
837 AdjustChildren(aPresContext, mChildInfosAfter, isHorizontal);
840 static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox,
841 nsIContent* aContent) {
842 // XXX Can this use GetPrimaryFrame?
843 for (nsIFrame* f : aParentBox->PrincipalChildList()) {
844 if (f->GetContent() == aContent) {
845 return f;
848 return nullptr;
851 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext,
852 nsTArray<nsSplitterInfo>& aChildInfos,
853 bool aIsHorizontal) {
854 /// printf("------- AdjustChildren------\n");
856 nsBoxLayoutState state(aPresContext);
858 for (auto& info : aChildInfos) {
859 nscoord newPref = info.pref + (info.changed - info.current);
860 if (nsIFrame* childBox =
861 GetChildBoxForContent(mParentBox, info.childElem)) {
862 SetPreferredSize(state, childBox, aIsHorizontal, newPref);
867 void nsSplitterFrameInner::SetPreferredSize(nsBoxLayoutState& aState,
868 nsIFrame* aChildBox,
869 bool aIsHorizontal, nscoord aSize) {
870 nsMargin margin(0, 0, 0, 0);
871 aChildBox->GetXULMargin(margin);
873 if (aIsHorizontal) {
874 aSize -= (margin.left + margin.right);
875 } else {
876 aSize -= (margin.top + margin.bottom);
879 RefPtr element = nsStyledElement::FromNode(aChildBox->GetContent());
880 if (!element) {
881 return;
884 // We set both the attribute and the CSS value, so that XUL persist="" keeps
885 // working, see bug 1790712.
887 int32_t pixels = aSize / AppUnitsPerCSSPixel();
888 nsAutoString attrValue;
889 attrValue.AppendInt(pixels);
890 element->SetAttr(aIsHorizontal ? nsGkAtoms::width : nsGkAtoms::height,
891 attrValue, IgnoreErrors());
893 nsCOMPtr<nsICSSDeclaration> decl = element->Style();
895 nsAutoCString cssValue;
896 cssValue.AppendInt(pixels);
897 cssValue.AppendLiteral("px");
898 decl->SetProperty(aIsHorizontal ? "width"_ns : "height"_ns, cssValue, ""_ns,
899 IgnoreErrors());
902 void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff,
903 nsTArray<nsSplitterInfo>& aChildInfos,
904 int32_t& aSpaceLeft) {
905 aSpaceLeft = 0;
907 for (auto& info : aChildInfos) {
908 nscoord min = info.min;
909 nscoord max = info.max;
910 nscoord& c = info.changed;
912 // figure our how much space to add or remove
913 if (c + aDiff < min) {
914 aDiff += (c - min);
915 c = min;
916 } else if (c + aDiff > max) {
917 aDiff -= (max - c);
918 c = max;
919 } else {
920 c += aDiff;
921 aDiff = 0;
924 // there is not space left? We are done
925 if (aDiff == 0) {
926 break;
930 aSpaceLeft = aDiff;
934 * Ok if we want to resize a child we will know the actual size in pixels we
935 * want it to be. This is not the preferred size. But the only way we can change
936 * a child is by manipulating its preferred size. So give the actual pixel size
937 * this method will figure out the preferred size and set it.
940 void nsSplitterFrameInner::ResizeChildTo(nscoord& aDiff) {
941 nscoord spaceLeft = 0;
943 if (!mChildInfosBefore.IsEmpty()) {
944 AddRemoveSpace(aDiff, mChildInfosBefore, spaceLeft);
945 // If there is any space left over remove it from the diff we were
946 // originally given.
947 aDiff -= spaceLeft;
950 AddRemoveSpace(-aDiff, mChildInfosAfter, spaceLeft);
952 if (spaceLeft != 0 && !mChildInfosAfter.IsEmpty()) {
953 aDiff += spaceLeft;
954 AddRemoveSpace(spaceLeft, mChildInfosBefore, spaceLeft);