зеркало из https://github.com/mozilla/gecko-dev.git
966 строки
29 KiB
C++
966 строки
29 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
//
|
|
// Eric Vaughan
|
|
// Netscape Communications
|
|
//
|
|
// See documentation in associated header file
|
|
//
|
|
|
|
#include "LayoutConstants.h"
|
|
#include "SimpleXULLeafFrame.h"
|
|
#include "gfxContext.h"
|
|
#include "mozilla/ReflowInput.h"
|
|
#include "nsSplitterFrame.h"
|
|
#include "nsGkAtoms.h"
|
|
#include "nsXULElement.h"
|
|
#include "nsPresContext.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "nsNameSpaceManager.h"
|
|
#include "nsScrollbarButtonFrame.h"
|
|
#include "nsIDOMEventListener.h"
|
|
#include "nsICSSDeclaration.h"
|
|
#include "nsFrameList.h"
|
|
#include "nsHTMLParts.h"
|
|
#include "mozilla/ComputedStyle.h"
|
|
#include "mozilla/CSSOrderAwareFrameIterator.h"
|
|
#include "nsContainerFrame.h"
|
|
#include "nsContentCID.h"
|
|
#include "nsLayoutUtils.h"
|
|
#include "nsDisplayList.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsFlexContainerFrame.h"
|
|
#include "mozilla/dom/Element.h"
|
|
#include "mozilla/dom/Event.h"
|
|
#include "mozilla/dom/MouseEvent.h"
|
|
#include "mozilla/MouseEvents.h"
|
|
#include "mozilla/PresShell.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "nsStyledElement.h"
|
|
|
|
using namespace mozilla;
|
|
|
|
using mozilla::dom::Element;
|
|
using mozilla::dom::Event;
|
|
|
|
class nsSplitterInfo {
|
|
public:
|
|
nscoord min;
|
|
nscoord max;
|
|
nscoord current;
|
|
nscoord pref;
|
|
nscoord changed;
|
|
nsCOMPtr<nsIContent> childElem;
|
|
};
|
|
|
|
enum class ResizeType {
|
|
// Resize the closest sibling in a given direction.
|
|
Closest,
|
|
// Resize the farthest sibling in a given direction.
|
|
Farthest,
|
|
// Resize only flexible siblings in a given direction.
|
|
Flex,
|
|
// No space should be taken out of any children in that direction.
|
|
// FIXME(emilio): This is a rather odd name...
|
|
Grow,
|
|
// Only resize adjacent siblings.
|
|
Sibling,
|
|
// Don't resize anything in a given direction.
|
|
None,
|
|
};
|
|
static ResizeType ResizeTypeFromAttribute(const Element& aElement,
|
|
nsAtom* aAttribute) {
|
|
static Element::AttrValuesArray strings[] = {
|
|
nsGkAtoms::farthest, nsGkAtoms::flex, nsGkAtoms::grow,
|
|
nsGkAtoms::sibling, nsGkAtoms::none, nullptr};
|
|
switch (aElement.FindAttrValueIn(kNameSpaceID_None, aAttribute, strings,
|
|
eCaseMatters)) {
|
|
case 0:
|
|
return ResizeType::Farthest;
|
|
case 1:
|
|
return ResizeType::Flex;
|
|
case 2:
|
|
// Grow only applies to resizeAfter.
|
|
if (aAttribute == nsGkAtoms::resizeafter) {
|
|
return ResizeType::Grow;
|
|
}
|
|
break;
|
|
case 3:
|
|
return ResizeType::Sibling;
|
|
case 4:
|
|
return ResizeType::None;
|
|
default:
|
|
break;
|
|
}
|
|
return ResizeType::Closest;
|
|
}
|
|
|
|
class nsSplitterFrameInner final : public nsIDOMEventListener {
|
|
protected:
|
|
virtual ~nsSplitterFrameInner();
|
|
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIDOMEVENTLISTENER
|
|
|
|
explicit nsSplitterFrameInner(nsSplitterFrame* aSplitter)
|
|
: mOuter(aSplitter) {}
|
|
|
|
void Disconnect() { mOuter = nullptr; }
|
|
|
|
nsresult MouseDown(Event* aMouseEvent);
|
|
nsresult MouseUp(Event* aMouseEvent);
|
|
nsresult MouseMove(Event* aMouseEvent);
|
|
|
|
void MouseDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
|
|
void MouseUp(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
|
|
|
|
void AdjustChildren(nsPresContext* aPresContext);
|
|
void AdjustChildren(nsPresContext* aPresContext,
|
|
nsTArray<nsSplitterInfo>& aChildInfos,
|
|
bool aIsHorizontal);
|
|
|
|
void AddRemoveSpace(nscoord aDiff, nsTArray<nsSplitterInfo>& aChildInfos,
|
|
int32_t& aSpaceLeft);
|
|
|
|
void ResizeChildTo(nscoord& aDiff);
|
|
|
|
void UpdateState();
|
|
|
|
void AddListener();
|
|
void RemoveListener();
|
|
|
|
enum class State { Open, CollapsedBefore, CollapsedAfter, Dragging };
|
|
enum CollapseDirection { Before, After };
|
|
|
|
ResizeType GetResizeBefore();
|
|
ResizeType GetResizeAfter();
|
|
State GetState();
|
|
|
|
bool SupportsCollapseDirection(CollapseDirection aDirection);
|
|
|
|
void EnsureOrient();
|
|
void SetPreferredSize(nsIFrame* aChildBox, bool aIsHorizontal, nscoord aSize);
|
|
|
|
nsSplitterFrame* mOuter;
|
|
bool mDidDrag = false;
|
|
nscoord mDragStart = 0;
|
|
nsIFrame* mParentBox = nullptr;
|
|
bool mPressed = false;
|
|
nsTArray<nsSplitterInfo> mChildInfosBefore;
|
|
nsTArray<nsSplitterInfo> mChildInfosAfter;
|
|
State mState = State::Open;
|
|
nscoord mSplitterPos = 0;
|
|
bool mDragging = false;
|
|
|
|
const Element* SplitterElement() const {
|
|
return mOuter->GetContent()->AsElement();
|
|
}
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(nsSplitterFrameInner, nsIDOMEventListener)
|
|
|
|
ResizeType nsSplitterFrameInner::GetResizeBefore() {
|
|
return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizebefore);
|
|
}
|
|
|
|
ResizeType nsSplitterFrameInner::GetResizeAfter() {
|
|
return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizeafter);
|
|
}
|
|
|
|
nsSplitterFrameInner::~nsSplitterFrameInner() = default;
|
|
|
|
nsSplitterFrameInner::State nsSplitterFrameInner::GetState() {
|
|
static Element::AttrValuesArray strings[] = {nsGkAtoms::dragging,
|
|
nsGkAtoms::collapsed, nullptr};
|
|
static Element::AttrValuesArray strings_substate[] = {
|
|
nsGkAtoms::before, nsGkAtoms::after, nullptr};
|
|
switch (SplitterElement()->FindAttrValueIn(
|
|
kNameSpaceID_None, nsGkAtoms::state, strings, eCaseMatters)) {
|
|
case 0:
|
|
return State::Dragging;
|
|
case 1:
|
|
switch (SplitterElement()->FindAttrValueIn(
|
|
kNameSpaceID_None, nsGkAtoms::substate, strings_substate,
|
|
eCaseMatters)) {
|
|
case 0:
|
|
return State::CollapsedBefore;
|
|
case 1:
|
|
return State::CollapsedAfter;
|
|
default:
|
|
if (SupportsCollapseDirection(After)) {
|
|
return State::CollapsedAfter;
|
|
}
|
|
return State::CollapsedBefore;
|
|
}
|
|
}
|
|
return State::Open;
|
|
}
|
|
|
|
//
|
|
// NS_NewSplitterFrame
|
|
//
|
|
// Creates a new Toolbar frame and returns it
|
|
//
|
|
nsIFrame* NS_NewSplitterFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
|
|
return new (aPresShell) nsSplitterFrame(aStyle, aPresShell->GetPresContext());
|
|
}
|
|
|
|
NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame)
|
|
|
|
nsSplitterFrame::nsSplitterFrame(ComputedStyle* aStyle,
|
|
nsPresContext* aPresContext)
|
|
: SimpleXULLeafFrame(aStyle, aPresContext, kClassID) {}
|
|
|
|
void nsSplitterFrame::DestroyFrom(nsIFrame* aDestructRoot,
|
|
PostDestroyData& aPostDestroyData) {
|
|
if (mInner) {
|
|
mInner->RemoveListener();
|
|
mInner->Disconnect();
|
|
mInner = nullptr;
|
|
}
|
|
SimpleXULLeafFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
|
|
}
|
|
|
|
nsresult nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID,
|
|
nsAtom* aAttribute,
|
|
int32_t aModType) {
|
|
nsresult rv =
|
|
SimpleXULLeafFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
|
|
if (aAttribute == nsGkAtoms::state) {
|
|
mInner->UpdateState();
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* Initialize us. If we are in a box get our alignment so we know what direction
|
|
* we are
|
|
*/
|
|
void nsSplitterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
|
|
nsIFrame* aPrevInFlow) {
|
|
MOZ_ASSERT(!mInner);
|
|
mInner = new nsSplitterFrameInner(this);
|
|
|
|
SimpleXULLeafFrame::Init(aContent, aParent, aPrevInFlow);
|
|
|
|
mInner->AddListener();
|
|
mInner->mParentBox = nullptr;
|
|
}
|
|
|
|
static bool IsValidParentBox(nsIFrame* aFrame) {
|
|
return aFrame->IsFlexContainerFrame();
|
|
}
|
|
|
|
static nsIFrame* GetValidParentBox(nsIFrame* aChild) {
|
|
return aChild->GetParent() && IsValidParentBox(aChild->GetParent())
|
|
? aChild->GetParent()
|
|
: nullptr;
|
|
}
|
|
|
|
void nsSplitterFrame::Reflow(nsPresContext* aPresContext,
|
|
ReflowOutput& aDesiredSize,
|
|
const ReflowInput& aReflowInput,
|
|
nsReflowStatus& aStatus) {
|
|
if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
|
|
mInner->mParentBox = GetValidParentBox(this);
|
|
mInner->UpdateState();
|
|
}
|
|
return SimpleXULLeafFrame::Reflow(aPresContext, aDesiredSize, aReflowInput,
|
|
aStatus);
|
|
}
|
|
|
|
static bool SplitterIsHorizontal(const nsIFrame* aParentBox) {
|
|
// If our parent is horizontal, the splitter is vertical and vice-versa.
|
|
MOZ_ASSERT(aParentBox->IsFlexContainerFrame());
|
|
const FlexboxAxisInfo info(aParentBox);
|
|
return !info.mIsRowOriented;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSplitterFrame::HandlePress(nsPresContext* aPresContext,
|
|
WidgetGUIEvent* aEvent,
|
|
nsEventStatus* aEventStatus) {
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext,
|
|
WidgetGUIEvent* aEvent,
|
|
nsEventStatus* aEventStatus,
|
|
bool aControlHeld) {
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSplitterFrame::HandleDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
|
|
nsEventStatus* aEventStatus) {
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSplitterFrame::HandleRelease(nsPresContext* aPresContext,
|
|
WidgetGUIEvent* aEvent,
|
|
nsEventStatus* aEventStatus) {
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
|
const nsDisplayListSet& aLists) {
|
|
SimpleXULLeafFrame::BuildDisplayList(aBuilder, aLists);
|
|
|
|
// if the mouse is captured always return us as the frame.
|
|
if (mInner->mDragging && aBuilder->IsForEventDelivery()) {
|
|
// XXX It's probably better not to check visibility here, right?
|
|
aLists.Outlines()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder, this);
|
|
return;
|
|
}
|
|
}
|
|
|
|
nsresult nsSplitterFrame::HandleEvent(nsPresContext* aPresContext,
|
|
WidgetGUIEvent* aEvent,
|
|
nsEventStatus* aEventStatus) {
|
|
NS_ENSURE_ARG_POINTER(aEventStatus);
|
|
if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
|
|
return NS_OK;
|
|
}
|
|
|
|
AutoWeakFrame weakFrame(this);
|
|
RefPtr<nsSplitterFrameInner> inner(mInner);
|
|
switch (aEvent->mMessage) {
|
|
case eMouseMove:
|
|
inner->MouseDrag(aPresContext, aEvent);
|
|
break;
|
|
|
|
case eMouseUp:
|
|
if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) {
|
|
inner->MouseUp(aPresContext, aEvent);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
NS_ENSURE_STATE(weakFrame.IsAlive());
|
|
return SimpleXULLeafFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
|
|
}
|
|
|
|
void nsSplitterFrameInner::MouseUp(nsPresContext* aPresContext,
|
|
WidgetGUIEvent* aEvent) {
|
|
if (mDragging && mOuter) {
|
|
AdjustChildren(aPresContext);
|
|
AddListener();
|
|
PresShell::ReleaseCapturingContent(); // XXXndeakin is this needed?
|
|
mDragging = false;
|
|
State newState = GetState();
|
|
// if the state is dragging then make it Open.
|
|
if (newState == State::Dragging) {
|
|
mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None,
|
|
nsGkAtoms::state, u""_ns, true);
|
|
}
|
|
|
|
mPressed = false;
|
|
|
|
// if we dragged then fire a command event.
|
|
if (mDidDrag) {
|
|
RefPtr<nsXULElement> element =
|
|
nsXULElement::FromNode(mOuter->GetContent());
|
|
element->DoCommand();
|
|
}
|
|
|
|
// printf("MouseUp\n");
|
|
}
|
|
|
|
mChildInfosBefore.Clear();
|
|
mChildInfosAfter.Clear();
|
|
}
|
|
|
|
void nsSplitterFrameInner::MouseDrag(nsPresContext* aPresContext,
|
|
WidgetGUIEvent* aEvent) {
|
|
if (!mDragging || !mOuter) {
|
|
return;
|
|
}
|
|
|
|
const bool isHorizontal = !mOuter->IsHorizontal();
|
|
nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
|
|
aEvent, RelativeTo{mParentBox});
|
|
nscoord pos = isHorizontal ? pt.x : pt.y;
|
|
|
|
// take our current position and subtract the start location,
|
|
// mDragStart is in parent-box relative coordinates already.
|
|
pos -= mDragStart;
|
|
|
|
for (auto& info : mChildInfosBefore) {
|
|
info.changed = info.current;
|
|
}
|
|
|
|
for (auto& info : mChildInfosAfter) {
|
|
info.changed = info.current;
|
|
}
|
|
nscoord oldPos = pos;
|
|
|
|
ResizeChildTo(pos);
|
|
|
|
State currentState = GetState();
|
|
bool supportsBefore = SupportsCollapseDirection(Before);
|
|
bool supportsAfter = SupportsCollapseDirection(After);
|
|
|
|
const bool isRTL =
|
|
mOuter->StyleVisibility()->mDirection == StyleDirection::Rtl;
|
|
bool pastEnd = oldPos > 0 && oldPos > pos;
|
|
bool pastBegin = oldPos < 0 && oldPos < pos;
|
|
if (isRTL) {
|
|
// Swap the boundary checks in RTL mode
|
|
std::swap(pastEnd, pastBegin);
|
|
}
|
|
const bool isCollapsedBefore = pastBegin && supportsBefore;
|
|
const bool isCollapsedAfter = pastEnd && supportsAfter;
|
|
|
|
// if we are in a collapsed position
|
|
if (isCollapsedBefore || isCollapsedAfter) {
|
|
// and we are not collapsed then collapse
|
|
if (currentState == State::Dragging) {
|
|
if (pastEnd) {
|
|
// printf("Collapse right\n");
|
|
if (supportsAfter) {
|
|
RefPtr<Element> outer = mOuter->mContent->AsElement();
|
|
outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"after"_ns,
|
|
true);
|
|
outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
|
|
true);
|
|
}
|
|
|
|
} else if (pastBegin) {
|
|
// printf("Collapse left\n");
|
|
if (supportsBefore) {
|
|
RefPtr<Element> outer = mOuter->mContent->AsElement();
|
|
outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"before"_ns,
|
|
true);
|
|
outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
|
|
true);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// if we are not in a collapsed position and we are not dragging make sure
|
|
// we are dragging.
|
|
if (currentState != State::Dragging) {
|
|
mOuter->mContent->AsElement()->SetAttr(
|
|
kNameSpaceID_None, nsGkAtoms::state, u"dragging"_ns, true);
|
|
}
|
|
AdjustChildren(aPresContext);
|
|
}
|
|
|
|
mDidDrag = true;
|
|
}
|
|
|
|
void nsSplitterFrameInner::AddListener() {
|
|
mOuter->GetContent()->AddEventListener(u"mouseup"_ns, this, false, false);
|
|
mOuter->GetContent()->AddEventListener(u"mousedown"_ns, this, false, false);
|
|
mOuter->GetContent()->AddEventListener(u"mousemove"_ns, this, false, false);
|
|
mOuter->GetContent()->AddEventListener(u"mouseout"_ns, this, false, false);
|
|
}
|
|
|
|
void nsSplitterFrameInner::RemoveListener() {
|
|
NS_ENSURE_TRUE_VOID(mOuter);
|
|
mOuter->GetContent()->RemoveEventListener(u"mouseup"_ns, this, false);
|
|
mOuter->GetContent()->RemoveEventListener(u"mousedown"_ns, this, false);
|
|
mOuter->GetContent()->RemoveEventListener(u"mousemove"_ns, this, false);
|
|
mOuter->GetContent()->RemoveEventListener(u"mouseout"_ns, this, false);
|
|
}
|
|
|
|
nsresult nsSplitterFrameInner::HandleEvent(dom::Event* aEvent) {
|
|
nsAutoString eventType;
|
|
aEvent->GetType(eventType);
|
|
if (eventType.EqualsLiteral("mouseup")) return MouseUp(aEvent);
|
|
if (eventType.EqualsLiteral("mousedown")) return MouseDown(aEvent);
|
|
if (eventType.EqualsLiteral("mousemove") ||
|
|
eventType.EqualsLiteral("mouseout"))
|
|
return MouseMove(aEvent);
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsSplitterFrameInner::MouseUp(Event* aMouseEvent) {
|
|
NS_ENSURE_TRUE(mOuter, NS_OK);
|
|
mPressed = false;
|
|
|
|
PresShell::ReleaseCapturingContent();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
template <typename LengthLike>
|
|
static nscoord ToLengthWithFallback(const LengthLike& aLengthLike,
|
|
nscoord aFallback) {
|
|
if (aLengthLike.ConvertsToLength()) {
|
|
return aLengthLike.ToLength();
|
|
}
|
|
return aFallback;
|
|
}
|
|
|
|
template <typename LengthLike>
|
|
static nsSize ToLengthWithFallback(const LengthLike& aWidth,
|
|
const LengthLike& aHeight,
|
|
nscoord aFallback = 0) {
|
|
return {ToLengthWithFallback(aWidth, aFallback),
|
|
ToLengthWithFallback(aHeight, aFallback)};
|
|
}
|
|
|
|
static void ApplyMargin(nsSize& aSize, const nsMargin& aMargin) {
|
|
if (aSize.width != NS_UNCONSTRAINEDSIZE) {
|
|
aSize.width += aMargin.LeftRight();
|
|
}
|
|
if (aSize.height != NS_UNCONSTRAINEDSIZE) {
|
|
aSize.height += aMargin.TopBottom();
|
|
}
|
|
}
|
|
|
|
nsresult nsSplitterFrameInner::MouseDown(Event* aMouseEvent) {
|
|
NS_ENSURE_TRUE(mOuter, NS_OK);
|
|
dom::MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
|
|
if (!mouseEvent) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// only if left button
|
|
if (mouseEvent->Button() != 0) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (SplitterElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
|
|
nsGkAtoms::_true, eCaseMatters))
|
|
return NS_OK;
|
|
|
|
mParentBox = GetValidParentBox(mOuter);
|
|
if (!mParentBox) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// get our index
|
|
mDidDrag = false;
|
|
|
|
EnsureOrient();
|
|
const bool isHorizontal = !mOuter->IsHorizontal();
|
|
|
|
const nsIContent* outerContent = mOuter->GetContent();
|
|
|
|
const ResizeType resizeBefore = GetResizeBefore();
|
|
const ResizeType resizeAfter = GetResizeAfter();
|
|
const int32_t childCount = mParentBox->PrincipalChildList().GetLength();
|
|
|
|
mChildInfosBefore.Clear();
|
|
mChildInfosAfter.Clear();
|
|
int32_t count = 0;
|
|
|
|
bool foundOuter = false;
|
|
CSSOrderAwareFrameIterator iter(
|
|
mParentBox, FrameChildListID::Principal,
|
|
CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
|
|
CSSOrderAwareFrameIterator::OrderState::Unknown,
|
|
CSSOrderAwareFrameIterator::OrderingProperty::Order);
|
|
for (; !iter.AtEnd(); iter.Next()) {
|
|
nsIFrame* childBox = iter.get();
|
|
if (childBox == mOuter) {
|
|
foundOuter = true;
|
|
if (!count) {
|
|
// We're at the beginning, nothing to do.
|
|
return NS_OK;
|
|
}
|
|
if (count == childCount - 1 && resizeAfter != ResizeType::Grow) {
|
|
// If it's the last index then we need to allow for resizeafter="grow"
|
|
return NS_OK;
|
|
}
|
|
}
|
|
count++;
|
|
|
|
nsIContent* content = childBox->GetContent();
|
|
// XXX flex seems untested, as it uses mBoxFlex rather than actual flexbox
|
|
// flex.
|
|
const nscoord flex = childBox->StyleXUL()->mBoxFlex;
|
|
const bool isBefore = !foundOuter;
|
|
const bool isResizable = [&] {
|
|
if (auto* element = nsXULElement::FromNode(content)) {
|
|
if (element->NodeInfo()->NameAtom() == nsGkAtoms::splitter) {
|
|
// skip over any splitters
|
|
return false;
|
|
}
|
|
|
|
// We need to check for hidden attribute too, since treecols with
|
|
// the hidden="true" attribute are not really hidden, just collapsed
|
|
if (element->GetXULBoolAttr(nsGkAtoms::fixed) ||
|
|
element->GetXULBoolAttr(nsGkAtoms::hidden)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// We need to check this here rather than in the switch before because we
|
|
// want `sibling` to work in the DOM order, not frame tree order.
|
|
if (resizeBefore == ResizeType::Sibling &&
|
|
content->GetNextElementSibling() == outerContent) {
|
|
return true;
|
|
}
|
|
if (resizeAfter == ResizeType::Sibling &&
|
|
content->GetPreviousElementSibling() == outerContent) {
|
|
return true;
|
|
}
|
|
|
|
const ResizeType resizeType = isBefore ? resizeBefore : resizeAfter;
|
|
switch (resizeType) {
|
|
case ResizeType::Grow:
|
|
case ResizeType::None:
|
|
case ResizeType::Sibling:
|
|
return false;
|
|
case ResizeType::Flex:
|
|
return flex > 0;
|
|
case ResizeType::Closest:
|
|
case ResizeType::Farthest:
|
|
break;
|
|
}
|
|
return true;
|
|
}();
|
|
|
|
if (!isResizable) {
|
|
continue;
|
|
}
|
|
|
|
nsSize curSize = childBox->GetSize();
|
|
const auto& pos = *childBox->StylePosition();
|
|
nsSize minSize = ToLengthWithFallback(pos.mMinWidth, pos.mMinHeight);
|
|
nsSize maxSize = ToLengthWithFallback(pos.mMaxWidth, pos.mMaxHeight,
|
|
NS_UNCONSTRAINEDSIZE);
|
|
nsSize prefSize(ToLengthWithFallback(pos.mWidth, curSize.width),
|
|
ToLengthWithFallback(pos.mHeight, curSize.height));
|
|
|
|
maxSize.width = std::max(maxSize.width, minSize.width);
|
|
maxSize.height = std::max(maxSize.height, minSize.height);
|
|
prefSize.width =
|
|
NS_CSS_MINMAX(prefSize.width, minSize.width, maxSize.width);
|
|
prefSize.height =
|
|
NS_CSS_MINMAX(prefSize.height, minSize.height, maxSize.height);
|
|
|
|
nsMargin m;
|
|
childBox->StyleMargin()->GetMargin(m);
|
|
|
|
ApplyMargin(curSize, m);
|
|
ApplyMargin(minSize, m);
|
|
ApplyMargin(maxSize, m);
|
|
ApplyMargin(prefSize, m);
|
|
|
|
auto& list = isBefore ? mChildInfosBefore : mChildInfosAfter;
|
|
nsSplitterInfo& info = *list.AppendElement();
|
|
info.childElem = content;
|
|
info.min = isHorizontal ? minSize.width : minSize.height;
|
|
info.max = isHorizontal ? maxSize.width : maxSize.height;
|
|
info.pref = isHorizontal ? prefSize.width : prefSize.height;
|
|
info.current = info.changed = isHorizontal ? curSize.width : curSize.height;
|
|
}
|
|
|
|
if (!foundOuter) {
|
|
return NS_OK;
|
|
}
|
|
|
|
mPressed = true;
|
|
|
|
const bool reverseDirection = [&] {
|
|
MOZ_ASSERT(mParentBox->IsFlexContainerFrame());
|
|
const FlexboxAxisInfo info(mParentBox);
|
|
if (!info.mIsRowOriented) {
|
|
return info.mIsMainAxisReversed;
|
|
}
|
|
const bool rtl =
|
|
mParentBox->StyleVisibility()->mDirection == StyleDirection::Rtl;
|
|
return info.mIsMainAxisReversed != rtl;
|
|
}();
|
|
|
|
if (reverseDirection) {
|
|
// The before array is really the after array, and the order needs to be
|
|
// reversed. First reverse both arrays.
|
|
mChildInfosBefore.Reverse();
|
|
mChildInfosAfter.Reverse();
|
|
|
|
// Now swap the two arrays.
|
|
std::swap(mChildInfosBefore, mChildInfosAfter);
|
|
}
|
|
|
|
// if resizebefore is not Farthest, reverse the list because the first child
|
|
// in the list is the farthest, and we want the first child to be the closest.
|
|
if (resizeBefore != ResizeType::Farthest) {
|
|
mChildInfosBefore.Reverse();
|
|
}
|
|
|
|
// if the resizeafter is the Farthest we must reverse the list because the
|
|
// first child in the list is the closest we want the first child to be the
|
|
// Farthest.
|
|
if (resizeAfter == ResizeType::Farthest) {
|
|
mChildInfosAfter.Reverse();
|
|
}
|
|
|
|
int32_t c;
|
|
nsPoint pt =
|
|
nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent, mParentBox);
|
|
if (isHorizontal) {
|
|
c = pt.x;
|
|
mSplitterPos = mOuter->mRect.x;
|
|
} else {
|
|
c = pt.y;
|
|
mSplitterPos = mOuter->mRect.y;
|
|
}
|
|
|
|
mDragStart = c;
|
|
|
|
// printf("Pressed mDragStart=%d\n",mDragStart);
|
|
|
|
PresShell::SetCapturingContent(mOuter->GetContent(),
|
|
CaptureFlags::IgnoreAllowedState);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsSplitterFrameInner::MouseMove(Event* aMouseEvent) {
|
|
NS_ENSURE_TRUE(mOuter, NS_OK);
|
|
if (!mPressed) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (mDragging) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this);
|
|
mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
|
|
u"dragging"_ns, true);
|
|
|
|
RemoveListener();
|
|
mDragging = true;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool nsSplitterFrameInner::SupportsCollapseDirection(
|
|
nsSplitterFrameInner::CollapseDirection aDirection) {
|
|
static Element::AttrValuesArray strings[] = {
|
|
nsGkAtoms::before, nsGkAtoms::after, nsGkAtoms::both, nullptr};
|
|
|
|
switch (SplitterElement()->FindAttrValueIn(
|
|
kNameSpaceID_None, nsGkAtoms::collapse, strings, eCaseMatters)) {
|
|
case 0:
|
|
return (aDirection == Before);
|
|
case 1:
|
|
return (aDirection == After);
|
|
case 2:
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static nsIFrame* SlowOrderAwareSibling(nsIFrame* aBox, bool aNext) {
|
|
nsIFrame* parent = aBox->GetParent();
|
|
if (!parent) {
|
|
return nullptr;
|
|
}
|
|
CSSOrderAwareFrameIterator iter(
|
|
parent, FrameChildListID::Principal,
|
|
CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
|
|
CSSOrderAwareFrameIterator::OrderState::Unknown,
|
|
CSSOrderAwareFrameIterator::OrderingProperty::Order);
|
|
|
|
nsIFrame* prevSibling = nullptr;
|
|
for (; !iter.AtEnd(); iter.Next()) {
|
|
nsIFrame* current = iter.get();
|
|
if (!aNext && current == aBox) {
|
|
return prevSibling;
|
|
}
|
|
if (aNext && prevSibling == aBox) {
|
|
return current;
|
|
}
|
|
prevSibling = current;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void nsSplitterFrameInner::UpdateState() {
|
|
// State Transitions:
|
|
// Open -> Dragging
|
|
// Open -> CollapsedBefore
|
|
// Open -> CollapsedAfter
|
|
// CollapsedBefore -> Open
|
|
// CollapsedBefore -> Dragging
|
|
// CollapsedAfter -> Open
|
|
// CollapsedAfter -> Dragging
|
|
// Dragging -> Open
|
|
// Dragging -> CollapsedBefore (auto collapse)
|
|
// Dragging -> CollapsedAfter (auto collapse)
|
|
|
|
State newState = GetState();
|
|
|
|
if (newState == mState) {
|
|
// No change.
|
|
return;
|
|
}
|
|
|
|
if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) &&
|
|
IsValidParentBox(mOuter->GetParent())) {
|
|
// Find the splitter's immediate sibling.
|
|
const bool prev =
|
|
newState == State::CollapsedBefore || mState == State::CollapsedBefore;
|
|
nsIFrame* splitterSibling = SlowOrderAwareSibling(mOuter, !prev);
|
|
if (splitterSibling) {
|
|
nsCOMPtr<nsIContent> sibling = splitterSibling->GetContent();
|
|
if (sibling && sibling->IsElement()) {
|
|
if (mState == State::CollapsedBefore ||
|
|
mState == State::CollapsedAfter) {
|
|
// CollapsedBefore -> Open
|
|
// CollapsedBefore -> Dragging
|
|
// CollapsedAfter -> Open
|
|
// CollapsedAfter -> Dragging
|
|
nsContentUtils::AddScriptRunner(new nsUnsetAttrRunnable(
|
|
sibling->AsElement(), nsGkAtoms::collapsed));
|
|
} else if ((mState == State::Open || mState == State::Dragging) &&
|
|
(newState == State::CollapsedBefore ||
|
|
newState == State::CollapsedAfter)) {
|
|
// Open -> CollapsedBefore / CollapsedAfter
|
|
// Dragging -> CollapsedBefore / CollapsedAfter
|
|
nsContentUtils::AddScriptRunner(new nsSetAttrRunnable(
|
|
sibling->AsElement(), nsGkAtoms::collapsed, u"true"_ns));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mState = newState;
|
|
}
|
|
|
|
void nsSplitterFrameInner::EnsureOrient() {
|
|
mOuter->mIsHorizontal = SplitterIsHorizontal(mParentBox);
|
|
}
|
|
|
|
void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext) {
|
|
EnsureOrient();
|
|
const bool isHorizontal = !mOuter->IsHorizontal();
|
|
|
|
AdjustChildren(aPresContext, mChildInfosBefore, isHorizontal);
|
|
AdjustChildren(aPresContext, mChildInfosAfter, isHorizontal);
|
|
}
|
|
|
|
static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox,
|
|
nsIContent* aContent) {
|
|
// XXX Can this use GetPrimaryFrame?
|
|
for (nsIFrame* f : aParentBox->PrincipalChildList()) {
|
|
if (f->GetContent() == aContent) {
|
|
return f;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext,
|
|
nsTArray<nsSplitterInfo>& aChildInfos,
|
|
bool aIsHorizontal) {
|
|
/// printf("------- AdjustChildren------\n");
|
|
|
|
for (auto& info : aChildInfos) {
|
|
nscoord newPref = info.pref + (info.changed - info.current);
|
|
if (nsIFrame* childBox =
|
|
GetChildBoxForContent(mParentBox, info.childElem)) {
|
|
SetPreferredSize(childBox, aIsHorizontal, newPref);
|
|
}
|
|
}
|
|
}
|
|
|
|
void nsSplitterFrameInner::SetPreferredSize(nsIFrame* aChildBox,
|
|
bool aIsHorizontal, nscoord aSize) {
|
|
nsMargin margin;
|
|
aChildBox->StyleMargin()->GetMargin(margin);
|
|
if (aIsHorizontal) {
|
|
aSize -= (margin.left + margin.right);
|
|
} else {
|
|
aSize -= (margin.top + margin.bottom);
|
|
}
|
|
|
|
RefPtr element = nsStyledElement::FromNode(aChildBox->GetContent());
|
|
if (!element) {
|
|
return;
|
|
}
|
|
|
|
// We set both the attribute and the CSS value, so that XUL persist="" keeps
|
|
// working, see bug 1790712.
|
|
|
|
int32_t pixels = aSize / AppUnitsPerCSSPixel();
|
|
nsAutoString attrValue;
|
|
attrValue.AppendInt(pixels);
|
|
element->SetAttr(aIsHorizontal ? nsGkAtoms::width : nsGkAtoms::height,
|
|
attrValue, IgnoreErrors());
|
|
|
|
nsCOMPtr<nsICSSDeclaration> decl = element->Style();
|
|
|
|
nsAutoCString cssValue;
|
|
cssValue.AppendInt(pixels);
|
|
cssValue.AppendLiteral("px");
|
|
decl->SetProperty(aIsHorizontal ? "width"_ns : "height"_ns, cssValue, ""_ns,
|
|
IgnoreErrors());
|
|
}
|
|
|
|
void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff,
|
|
nsTArray<nsSplitterInfo>& aChildInfos,
|
|
int32_t& aSpaceLeft) {
|
|
aSpaceLeft = 0;
|
|
|
|
for (auto& info : aChildInfos) {
|
|
nscoord min = info.min;
|
|
nscoord max = info.max;
|
|
nscoord& c = info.changed;
|
|
|
|
// figure our how much space to add or remove
|
|
if (c + aDiff < min) {
|
|
aDiff += (c - min);
|
|
c = min;
|
|
} else if (c + aDiff > max) {
|
|
aDiff -= (max - c);
|
|
c = max;
|
|
} else {
|
|
c += aDiff;
|
|
aDiff = 0;
|
|
}
|
|
|
|
// there is not space left? We are done
|
|
if (aDiff == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
aSpaceLeft = aDiff;
|
|
}
|
|
|
|
/**
|
|
* Ok if we want to resize a child we will know the actual size in pixels we
|
|
* want it to be. This is not the preferred size. But the only way we can change
|
|
* a child is by manipulating its preferred size. So give the actual pixel size
|
|
* this method will figure out the preferred size and set it.
|
|
*/
|
|
|
|
void nsSplitterFrameInner::ResizeChildTo(nscoord& aDiff) {
|
|
nscoord spaceLeft = 0;
|
|
|
|
if (!mChildInfosBefore.IsEmpty()) {
|
|
AddRemoveSpace(aDiff, mChildInfosBefore, spaceLeft);
|
|
// If there is any space left over remove it from the diff we were
|
|
// originally given.
|
|
aDiff -= spaceLeft;
|
|
}
|
|
|
|
AddRemoveSpace(-aDiff, mChildInfosAfter, spaceLeft);
|
|
|
|
if (spaceLeft != 0 && !mChildInfosAfter.IsEmpty()) {
|
|
aDiff += spaceLeft;
|
|
AddRemoveSpace(spaceLeft, mChildInfosBefore, spaceLeft);
|
|
}
|
|
}
|