Giter VIP home page Giter VIP logo

Comments (4)

weisJ avatar weisJ commented on June 15, 2024 1

I guess I'm mostly confused with the meaning of 'action'

The actions are "actions" that manipulate the applications model. Eve though button click/press is an action in the world of swing, we delegate those to our action classes.


I patched together an example of how I imagine all the APIs will go together.
It's just a simple counter which can be incremented using a button as long as the current counter value is less than 10.

Some general notes:

  • The EventBus API is rather low level and used by higher level APIs. Most parts of the application won't have to deal with it directly.
  • The annotations @Model and @ViewProviderFor aren't fully worked out yet. Their exact semantics depend on the implementation of the Application class returned by the ApplicationManager.
  • In the world of MVC:
    • CounterModel is the model.
    • CounterViewProvider "is" the view. (Depending on the implementation of @ViewProviderFor there might be no createView method at all).
    • Actions (i.e. AnAction) are the controllers. Of course more complex views and models may need an intermediate controller which will be bound to the view. Smaller controllers though are simply wired when creating the view itself.
  • We will need some bridge classes to mediate between the models of swing (i.e. for SelectionModel, TableModel, TreeModel etc.) and our own models
    @AutoService(AppComponent.class)
    class CounterAppComponent implements AppComponent {

        private final CounterModel model;

        public CounterAppComponent() {
            model = ApplicationManager.getApplication().getModel(CounterModel.class);
        }

        @Override
        public String getTitle() {
            return "Counter";
        }

        @Override
        public Alignment getPreferredPosition() {
            return Alignment.NORTH_WEST;
        }

        @Override
        public JComponent getComponent() {
            return UIViewFactory.createView(model);
        }
    }


    @Model
    class CounterModel implements Observable {

        public static final String COUNT_PROPERTY = "count";

        private IntProperty count = new IntProperty();

        public int getCount() {
            ServiceLoader.loadInstalled()
            return count.getValue();
        }

        public void setCount(final int count) {
            count.setValue(count);
        }
    }

    @AutoService(AnAction.class)
    class IncrementAction implements AnAction {

        @Override
        public String getIdentifier() {
            return "increment_counter";
        }

        @Override
        public String getDisplayName() {
            return "Increment";
        }

        @Override
        public boolean isEnabled() {
            var model = getCounterModel();
            return model != null && model.getCount() <= 10;
        }

        @Override
        public void runAction() {
            var model = getCounterModel();
            if (model == null) return;
            model.setCount(model.getCount() + 1);
        }

        private CounterModel getCounterModel() {
            return ApplicationManager.getApplication().getModelIfCreated(CounterModel.class);
        }
    }

    @ViewProviderFor(model = CounterModel.class)
    class CounterViewProvider implements ViewProvider<CounterModel> {

        @Override
        public JComponent createView(final CounterModel model) {
            JLabel counterLabel = new JLabel();
            model.observe(CounterModel.COUNT_PROPERTY, (oldValue, newValue) -> {
                counterLabel.setText(Objects.toString(newValue));
            });
            JPanel holder = new JPanel(new BorderLayout());
            JPanel labelHolder = new JPanel(new GridBagLayout());
            labelHolder.add(counterLabel);
            holder.add(counterLabel, BorderLayout.CENTER);

            JComponent buttonPanel = Box.createHorizontalBox();
            buttonPanel.add(Box.createHorizontalGlue());

            JButton incrementButton = new JButton("Increment Counter");

            // Because this would be a common pattern this could be extracted into a helper method:
            incrementButton.addActionListener(e -> {
                EventBus.get(Topic.ACTIONS).post("increment_counter");
            });

            buttonPanel.add(incrementButton);
            buttonPanel.add(Box.createHorizontalGlue());

            return holder;
        }
    }

from mylibrelab.

weisJ avatar weisJ commented on June 15, 2024

Something else to consider is how we manage sorting of actions. Because we are loading actions as a service their order isn't guaranteed to be the same between builds.

We could sort them alphabetically but this wouldn't make sense for an action group containing "Undo" and "Redo" (which should be sorted in this order).

Then again how do we sort groups in their hierarchy i.e. in the top level group "Edit" (Which corresponds to the "Edit" menu in the menubar we most likely want the "Undo/Redo" group to show up at the top of the popup menu.

Maybe we can get away with some sort of "Priority" flag for actions and action groups wherein we first sort by priority and then by some comparator the action group provides (alphabetical would be the default for groups which are unaware of their content).

The example above could then look something like this:

  • Edit Group (Sorting = Priority first then alphabetical)
    • Undo/Redo Group (Priority = High, Sorting = [Undo, Redo])
      • Redo Action
      • Undo Action
    • Some other group B (Priority = Default, Sorting = Priority first then alphabetical)
      • Action B.1 (Priority = Low)
      • Action B.2 (Priority = Default)
      • Action B.3 (Priority = High)
    • Some other group A (Priority = Default, Sorting = Priority first then alphabetical)
      • Action A.1 (Priority = Default)
      • Action A.2 (Priority = High)
    • Some standalone action B (Priority = Default)
    • Some standalone action A (Priority = Default)
    • Some standalone action C (Priority = High)

Priority = Default is implicit for all groups and actions. For groups Sorting = Priority first then alphabetical is implicit.
Because the standalone actions don't belong to any intermediate group they implicitly belong to the "unnamed_group" which has Priority = Lowest and Sorting = Priority first then alphabetical. The resulting ordering then will be

  • Edit Group
    • Undo/Redo Group
      • Undo Action
      • Redo Action
    • Some other group A
      • Action A.2
      • Action A.3
    • Some other group B
      • Action B.3
      • Action B.2
      • Action B.1
  • Some standalone action C
  • Some standalone action A
  • Some standalone action B

For consistency the alphabetical ordering shouldn't be dictated by the display name but rather an internal identifier of the action (group).


I'm not fully satisfied with this approach as I'm unsure whether it gives us enough flexibility in controlling menu structure and I'd be happy to get some feedback on it.

from mylibrelab.

marko-radosavljevic avatar marko-radosavljevic commented on June 15, 2024

I think we shouldn't worry too much about details like this. It's impossible to get design perfectly right the first time, code is meant to be changed and rewritten. We should just focus on making those changes as easy as possible. Action order changes rarely, and nothing really depends on it, so fixing design problems around it should be trivial. So sorting by priority is fine, until we find a better solution.

That said, I'm struggling to connect actions to GUI components. It would be nice if you would walk me through everything event/action/component/DI related from the application start. I guess I'm mostly confused with the meaning of 'action'. I assume 'action' is some action user performed, like button click/press, so 'action' is component specific action that generates event that we broadcast over the bus to everyone interested? Or action is piece of code that processes an event (eg. something that would implement ActionListener)?

from mylibrelab.

marko-radosavljevic avatar marko-radosavljevic commented on June 15, 2024

Ah, thanks, now I have a full picture. I was thinking on different level of abstraction, more about event bus than high level MVC implementation. 👍

from mylibrelab.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.