dlsc-software-consulting-gmbh / CalendarFX

A Java framework for creating sophisticated calendar views (JavaFX 8, 9, 10, and 11)
http://www.dlsc.com
Apache License 2.0
791 stars 171 forks source link

Pinning an entry on top of other entries #110

Closed adrianjaroszewicz closed 3 years ago

adrianjaroszewicz commented 4 years ago

Hello Dirk, I would like to ask you about the possibility of drawing an entry on top of other entries (in ResourceCalendarView). As far as I know there is no such possibility at the moment - am I right?

Would you consider adding it? The given entry would not be taken into account while calculating the overlapping (it would always stay on the top): calendarfx_entry_on_top

The API I imagine could look like com.calendarfx.view.EntryViewBase#setOnTop(boolean) - so that we could use it in our extension of EntryViewBase like this:

setOnTop(true);
setPrefWidth(50);
setAlignmentStrategy(AlignmentStrategy.ALIGN_RIGHT);

What do you think about it?

Cheers, Adrian

dlemmermann commented 4 years ago

Something like that could be done, I guess. Three ways to proceed:

  1. you add this capability and submit a pull request
  2. you hire me
  3. PSI becomes a sponsor of my work: https://github.com/sponsors/dlsc-software-consulting-gmbh https://github.com/sponsors/dlsc-software-consulting-gmbh

Sorry Adrian, but I need to make a living too and I have to juggle many projects at the same time.

Dirk

On Oct 15, 2020, at 12:28 PM, adrianjaroszewicz notifications@github.com wrote:

Hello Dirk, I would like to ask you about the possibility of drawing an entry on top of other entries (in ResourceCalendarView). As far as I know there is no such possibility at the moment - am I right?

Would you consider adding it? The given entry would not be taken into account while calculating the overlapping (it would always stay on the top): https://user-images.githubusercontent.com/34687474/96107781-1d799280-0edd-11eb-920e-b6d9d6d1d74a.png The API I imagine could look like com.calendarfx.view.EntryViewBase#setOnTop(boolean) - so that we could use it in our extension of EntryViewBase like this:

setOnTop(true); setPrefWidth(50); setAlignmentStrategy(AlignmentStrategy.ALIGN_RIGHT); What do you think about it?

Cheers, Adrian

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/110, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACIXWXLHWLQBYTWKRUDVDD3SK3FDHANCNFSM4SRZOQDA.

adrianjaroszewicz commented 4 years ago

Sure, that makes sense to me :) I will contact my colleagues and will let you know about our decision.

Have a nice day! Adrian

adrianjaroszewicz commented 3 years ago

Hello Dirk,

I'm sorry for not contacting you in this manner but our customer has postponed the development of this feature for some time. But after a couple of months he came back to it - so our management has decided to go with the first option that you have suggested.

We have an idea of how the implementation could look like - but we wanted to ask for your feedback first, in order not to go in a wrong direction.

The idea is to add a new method impl.com.calendarfx.view.DayViewSkin#layoutEntryViewsOnTop, here is a draft implementation:

private void layoutEntryViewsOnTop(List<DayEntryView> entryViews, DayView dayView, double contentX, double contentY, double contentWidth, double contentHeight) {
    List<DayEntryView> entryViews = entryViews.stream().filter(EntryViewBase::isOnTop).collect(Collectors.toList());
    for(DayEntryView entryView : entryViews)
    {
        Entry<?> entry = entryView.getEntry();

        double y1;
        double y2;

        if (dayView.isScrollingEnabled()) {

            y1 = dayView.getLocation(entry.getStartAsZonedDateTime());
            y2 = dayView.getLocation(entry.getEndAsZonedDateTime());

        } else {

            y1 = dayView.getLocation(entry.getStartTime());
            y2 = dayView.getLocation(entry.getEndTime());
        }
        double minHeight = entryView.minHeight(contentWidth);
        entryView.resizeRelocate(snapPositionX(contentX + 60), snapPositionY(y1),
                snapSizeX(contentWidth / 2), snapSizeY(Math.max(minHeight, y2 - y1 - 2)));
        entryView.toFront();
    }
}

and call it after each call of impl.com.calendarfx.view.DayViewSkin#layoutEntryViews. Additionaly, the list of entry views that are given as an input to the impl.com.calendarfx.view.DayViewSkin#layoutEntryViews method would be filtered so that entries that are on the top would not be passed there.

Of course this is just a draft, in the final solution we would take the alignment information into account (com.calendarfx.view.EntryViewBase#getAlignmentStrategy) + a property that tells what should be the width of this entry (we were thinking about introducing a property that keeps the information about a percentage width of this entry in the context of the whole column). It would be added to the EntryViewBase class:

    private final DoubleProperty widthPercentage = new SimpleDoubleProperty(this, "widthPercentage", 100) {

        @Override
        public void set(double newValue) {
            if (newValue < 10 || newValue > 100) {
                throw new IllegalArgumentException("percentage width must be between 10 and 100 but was " + newValue);
            }
            super.set(newValue);
        }
    };

    /**
     * A percentage value used to specify how much of the available width inside the
     * view will be utilized by the entry views. The default value is 100%, however
     * applications might want to set a smaller value to allow the user to click and
     * create new entries in already used time intervals.
     *
     * @return the entry percentage width
     */
    public final DoubleProperty widthPercentageProperty() {
        return widthPercentage;
    }

    /**
     * Sets the value of {@link #widthPercentage}.
     *
     * @param percentage the new percentage width
     */
    public final void setWidthPercentage(double percentage) {
        this.widthPercentage.set(percentage);
    }

    /**
     * Returns the value of {@link #widthPercentageProperty()}.
     *
     * @return the percentage width
     */
    public double getWidthPercentage() {
        return WidthPercentage.get();
    }

What do you think about this direction? Is this something you would like to include to calendarx? We are opened to any suggestions so please let me know what's your opinion :)

Cheers, Adrian Jaroszewicz

dlemmermann commented 3 years ago

This looks good to me so far, but what about the resolver classes that check for overlapping entries in Swimlane layout. Those also need to check for the “onTop” property and ignore those that are on top.

On Mar 19, 2021, at 10:26 AM, adrianjaroszewicz @.***> wrote:

Hello Dirk,

I'm sorry for not contacting you in this manner but our customer has postponed the development of this feature for some time. But after a couple of months he came back to it - so our management has decided to go with the first option that you have suggested.

We have an idea of how the implementation could look like - but we wanted to ask for your feedback first, in order not to go in a wrong direction.

The idea is to add a new method impl.com.calendarfx.view.DayViewSkin#layoutEntryViewsOnTop, here is a draft implementation:

private void layoutEntryViewsOnTop(List entryViews, DayView dayView, double contentX, double contentY, double contentWidth, double contentHeight) { List entryViews = entryViews.stream().filter(EntryViewBase::isOnTop).collect(Collectors.toList()); for(DayEntryView entryView : entryViews) { Entry<?> entry = entryView.getEntry();

  double y1;
  double y2;

  if (dayView.isScrollingEnabled()) {

      y1 = dayView.getLocation(entry.getStartAsZonedDateTime());
      y2 = dayView.getLocation(entry.getEndAsZonedDateTime());

  } else {

      y1 = dayView.getLocation(entry.getStartTime());
      y2 = dayView.getLocation(entry.getEndTime());
  }
  double minHeight = entryView.minHeight(contentWidth);
  entryView.resizeRelocate(snapPositionX(contentX + 60), snapPositionY(y1),
          snapSizeX(contentWidth / 2), snapSizeY(Math.max(minHeight, y2 - y1 - 2)));
  entryView.toFront();

} } and call it after each call of impl.com.calendarfx.view.DayViewSkin#layoutEntryViews. Additionaly, the list of entry views that are given as an input to the impl.com.calendarfx.view.DayViewSkin#layoutEntryViews method would be filtered so that entries that are on the top would not be passed there.

Of course this is just a draft, in the final solution we would take the alignment information into account (com.calendarfx.view.EntryViewBase#getAlignmentStrategy) + a property that tells what should be the width of this entry (we were thinking about introducing a property that keeps the information about a percentage width of this entry in the context of the whole column). It would be added to the EntryViewBase class:

private final DoubleProperty widthPercentage = new SimpleDoubleProperty(this, "widthPercentage", 100) {

    @Override
    public void set(double newValue) {
        if (newValue < 10 || newValue > 100) {
            throw new IllegalArgumentException("percentage width must be between 10 and 100 but was " + newValue);
        }
        super.set(newValue);
    }
};

/**
 * A percentage value used to specify how much of the available width inside the
 * view will be utilized by the entry views. The default value is 100%, however
 * applications might want to set a smaller value to allow the user to click and
 * create new entries in already used time intervals.
 *
 * @return the entry percentage width
 */
public final DoubleProperty widthPercentageProperty() {
    return widthPercentage;
}

/**
 * Sets the value of ***@***.*** #widthPercentage}.
 *
 * @param percentage the new percentage width
 */
public final void setWidthPercentage(double percentage) {
    this.widthPercentage.set(percentage);
}

/**
 * Returns the value of ***@***.*** #widthPercentageProperty()}.
 *
 * @return the percentage width
 */
public double getWidthPercentage() {
    return WidthPercentage.get();
}

What do you think about this direction? Is this something you would like to include to calendarx? We are opened to any suggestions so please let me know what's your opinion :)

Cheers, Adrian Jaroszewicz

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/dlsc-software-consulting-gmbh/CalendarFX/issues/110#issuecomment-802683947, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACIXWXJJG2QIEQCG2XMNC4TTEMKFNANCNFSM4SRZOQDA.

adrianjaroszewicz commented 3 years ago

Thank you for the feedback! Do you mean impl.com.calendarfx.view.util.VisualBoundsResolver#resolve and impl.com.calendarfx.view.util.TimeBoundsResolver#resolve? Yes, that is probably a good idea to filter out entries that are not on top inside both these methods (and not only change the input to the impl.com.calendarfx.view.DayViewSkin#layoutEntryViews method) - in order anyone wants to use these resolve methods outside of the DayViewSkin. Ok, so we are starting the development - and will let you know about the results. Have a nice day! Adrian

dlemmermann commented 3 years ago

Yes, those are the ones I meant.

adrianjaroszewicz commented 3 years ago

Hello Dirk,

we have prepared a pull request with changes that introduce the possibility of putting entries on top of other entries. Could you please look at it and check if this is acceptable for you and can be merged to CalendarFX? https://github.com/dlsc-software-consulting-gmbh/CalendarFX/pull/123

Here is our reasoning behind the changes:

Also, while implementing the new feature, we have found two things that possbily might be bugs. 1) When using Alignment Strategy different than FILL and not defining value of preferred width of view entry, all entries have no width, and are not visible. 2) When using Alignment Strategy ALIGN_RIGHT in Swimlane mode, the offset of entry was not correctly computed:

    switch (entryView.getAlignmentStrategy()) {
        case ALIGN_RIGHT:
            x = columnWidth - w;
            break;

the correct formula probably is: x = x + (columnWidth - w). Our changes are addressing those two problems: In 1) when no preferred width value is present, we simply use all available space (columnWidth or contentWidth). In 2) the created function adds column offset.

Regarding the filtering in resolver classes, we have introduced it in layoutStandard and layoutSwimlane methods. My friends suggested that I might have misunderstood you and it should be enough to have the filtering there and not introduce it additionally in resolver classes - but please let me know if our understanding is right here.

Of course please let me know in case of any comments/suggestions. I hope the code is fine and meets the standards of CalendarFX project :)

Cheers, Adrian

dlemmermann commented 3 years ago

PR looks pretty good already. Please see my comments there. I think it is important to have at least one sample app that highlights this feature and also do not use it in the samples that are already there.

adrianjaroszewicz commented 3 years ago

Sure thing, we added the sample. I hope it's fine - if you could take a look that would be great.

adrianjaroszewicz commented 3 years ago

Sorry for updating but it looks like our customer really needs this feature - so we would be grateful if you could check it before Easter. Do you think it is possible? If not and you're already on vacation, please don't bother, we can wait :)

dlemmermann commented 3 years ago

I won't be able to get to it. But whatever the final PR, it will support layers. Until then why don't you just stay on your fork?

adrianjaroszewicz commented 3 years ago

That makes sense, we're going to go this direction :) Happy Easter!