Revised the rendering and selection mechanism in ComponentList

This commit is contained in:
Kai S. K. Engelbart 2020-03-21 16:10:59 +01:00
parent bc30e5cb38
commit 2eefaaf862
21 changed files with 242 additions and 307 deletions

View File

@ -4,7 +4,7 @@ import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import envoy.client.net.WriteProxy; import envoy.client.net.WriteProxy;
import envoy.client.ui.list.ComponentListModel; import envoy.client.ui.list.Model;
import envoy.data.Message; import envoy.data.Message;
import envoy.data.Message.MessageStatus; import envoy.data.Message.MessageStatus;
import envoy.data.User; import envoy.data.User;
@ -28,7 +28,7 @@ public class Chat implements Serializable {
private static final long serialVersionUID = -7751248474547242056L; private static final long serialVersionUID = -7751248474547242056L;
private final User recipient; private final User recipient;
private final ComponentListModel<Message> model = new ComponentListModel<>(); private final Model<Message> model = new Model<>();
/** /**
* Provides the list of messages that the recipient receives.<br> * Provides the list of messages that the recipient receives.<br>
@ -80,7 +80,7 @@ public class Chat implements Serializable {
* @return all messages in the current chat * @return all messages in the current chat
* @since Envoy v0.1-alpha * @since Envoy v0.1-alpha
*/ */
public ComponentListModel<Message> getModel() { return model; } public Model<Message> getModel() { return model; }
/** /**
* @return the recipient of a message * @return the recipient of a message

View File

@ -112,13 +112,19 @@ public class Settings {
* @param theme the {@link Theme} to add * @param theme the {@link Theme} to add
* @since Envoy v0.2-alpha * @since Envoy v0.2-alpha
*/ */
public void addNewThemeToMap(Theme theme) { settings.getThemes().put(theme.getThemeName(), theme); } public void addNewThemeToMap(Theme theme) { getThemes().put(theme.getThemeName(), theme); }
/** /**
* @return the name of the currently active {@link Theme} * @return the name of the currently active {@link Theme}
* @since Envoy v0.2-alpha * @since Envoy v0.2-alpha
*/ */
public String getCurrentTheme() { return (String) items.get("currentTheme").get(); } public String getCurrentThemeName() { return (String) items.get("currentTheme").get(); }
/**
* @return the currently active {@link Theme}
* @since Envoy v0.1-beta
*/
public Theme getCurrentTheme() { return getTheme(getCurrentThemeName()); }
/** /**
* Sets the name of the current {@link Theme}. * Sets the name of the current {@link Theme}.

View File

@ -1,4 +1,4 @@
package envoy.client.ui.renderer; package envoy.client.ui;
import java.awt.Component; import java.awt.Component;
import java.awt.Dimension; import java.awt.Dimension;
@ -8,45 +8,36 @@ import javax.swing.*;
import envoy.client.data.Settings; import envoy.client.data.Settings;
import envoy.client.event.SendEvent; import envoy.client.event.SendEvent;
import envoy.client.ui.Color;
import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentList;
import envoy.client.ui.list.ComponentListCellRenderer;
import envoy.client.ui.primary.PrimaryButton; import envoy.client.ui.primary.PrimaryButton;
import envoy.data.User; import envoy.data.User;
import envoy.event.ContactOperationEvent; import envoy.event.ContactOperationEvent;
import envoy.event.EventBus; import envoy.event.EventBus;
/** /**
* Defines how a contact is displayed.<br> * Project: <strong>envoy-client</strong>
* <br> * File: <strong>ContactSearchComponent.java</strong>
* Project: <strong>envoy-client</strong><br> * Created: <strong>21.03.2020</strong>
* File: <strong>ContactsSearchRenderer.java</strong><br>
* Created: <strong>08.02.2020</strong><br>
* *
* @author Maximilian K&auml;fer
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy v0.1-beta
*/ */
public class ContactsSearchRenderer implements ComponentListCellRenderer<User> { public class ContactSearchComponent extends JComponent {
@Override private static final long serialVersionUID = 3166795412575239455L;
public JComponent getListCellComponent(ComponentList<? extends User> list, User user, boolean isSelected) {
final JPanel panel = new JPanel(); public ContactSearchComponent(ComponentList<? extends User> list, User user) {
panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
if (isSelected) {
panel.setBackground(Color.DARK_GRAY); setBackground(list.getBackground());
panel.setForeground(Color.RED); setForeground(list.getForeground());
} else {
panel.setBackground(list.getBackground());
panel.setForeground(list.getForeground());
}
JLabel display = new JLabel(user.getName()); JLabel display = new JLabel(user.getName());
display.setForeground(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getMessageTextColor()); display.setForeground(Settings.getInstance().getCurrentTheme().getMessageTextColor());
display.setAlignmentX(Component.LEFT_ALIGNMENT); display.setAlignmentX(Component.LEFT_ALIGNMENT);
display.setAlignmentY(Component.CENTER_ALIGNMENT); display.setAlignmentY(Component.CENTER_ALIGNMENT);
display.setFont(new Font("Arial", Font.PLAIN, 16)); display.setFont(new Font("Arial", Font.PLAIN, 16));
panel.add(display); add(display);
PrimaryButton add = new PrimaryButton("+"); PrimaryButton add = new PrimaryButton("+");
add.setFont(new Font("Arial", Font.PLAIN, 19)); add.setFont(new Font("Arial", Font.PLAIN, 19));
@ -63,17 +54,15 @@ public class ContactsSearchRenderer implements ComponentListCellRenderer<User> {
EventBus.getInstance().dispatch(new SendEvent(contactsOperationEvent)); EventBus.getInstance().dispatch(new SendEvent(contactsOperationEvent));
}); });
panel.add(add); add(add);
// Define some space to the messages below // Define some space to the messages below
panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 0, 15, 0), BorderFactory.createEtchedBorder())); setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 0, 15, 0), BorderFactory.createEtchedBorder()));
// Define a maximum height of 50px // Define a maximum height of 50px
Dimension size = new Dimension(435, 50); Dimension size = new Dimension(435, 50);
panel.setMaximumSize(size); setMaximumSize(size);
panel.setMinimumSize(size); setMinimumSize(size);
panel.setPreferredSize(size); setPreferredSize(size);
return panel;
} }
} }

View File

@ -36,12 +36,12 @@ public class MessageComponent extends JPanel {
} }
} }
public MessageComponent(ComponentList<? extends Message> list, Message message, boolean isSelected, long senderId) { public MessageComponent(ComponentList<? extends Message> list, Message message, long senderId) {
this(list.getMaximumSize().width, message, isSelected, senderId); this(list.getMaximumSize().width, message, senderId);
} }
public MessageComponent(int width, Message message, boolean isSelected, long senderId) { public MessageComponent(int width, Message message, long senderId) {
final var theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); final var theme = Settings.getInstance().getCurrentTheme();
final int padding = (int) (width * 0.35); final int padding = (int) (width * 0.35);
GridBagLayout gbl_panel = new GridBagLayout(); GridBagLayout gbl_panel = new GridBagLayout();
@ -51,7 +51,7 @@ public class MessageComponent extends JPanel {
gbl_panel.rowWeights = new double[] { 1, 1 }; gbl_panel.rowWeights = new double[] { 1, 1 };
setLayout(gbl_panel); setLayout(gbl_panel);
setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()); setBackground(theme.getCellColor());
// Date Label - The Label that displays the creation date of a message // Date Label - The Label that displays the creation date of a message
var dateLabel = new JLabel(new SimpleDateFormat("dd.MM.yyyy HH:mm").format(message.getCreationDate())); var dateLabel = new JLabel(new SimpleDateFormat("dd.MM.yyyy HH:mm").format(message.getCreationDate()));

View File

@ -0,0 +1,62 @@
package envoy.client.ui;
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JComponent;
import javax.swing.JLabel;
import envoy.client.data.Settings;
import envoy.client.ui.list.ComponentList;
import envoy.data.User;
import envoy.data.User.UserStatus;
/**
* Project: <strong>envoy-client</strong>
* File: <strong>UserComponent.java</strong>
* Created: <strong>21.03.2020</strong>
*
* @author Kai S. K. Engelbart
* @since Envoy v0.1-beta
*/
public class UserComponent extends JComponent {
private static final long serialVersionUID = 8450602172939729585L;
public UserComponent(ComponentList<? extends User> list, User user) {
final Theme theme = Settings.getInstance().getCurrentTheme();
setLayout(new BorderLayout());
// Panel background
setBackground(theme.getCellColor());
setOpaque(true);
setPreferredSize(new Dimension(100, 35));
// TODO add profile picture support in BorderLayout.West
JLabel username = new JLabel(user.getName());
username.setForeground(theme.getUserNameColor());
add(username, BorderLayout.CENTER);
final UserStatus status = user.getStatus();
JLabel statusLabel = new JLabel(status.toString());
Color foreground;
switch (status) {
case AWAY:
foreground = Color.yellow;
break;
case BUSY:
foreground = Color.blue;
break;
case ONLINE:
foreground = Color.green;
break;
default:
foreground = Color.lightGray;
break;
}
statusLabel.setForeground(foreground);
add(statusLabel, BorderLayout.NORTH);
}
}

View File

@ -22,16 +22,13 @@ import envoy.client.event.MessageCreationEvent;
import envoy.client.event.ThemeChangeEvent; import envoy.client.event.ThemeChangeEvent;
import envoy.client.net.Client; import envoy.client.net.Client;
import envoy.client.net.WriteProxy; import envoy.client.net.WriteProxy;
import envoy.client.ui.ContextMenu; import envoy.client.ui.*;
import envoy.client.ui.Theme;
import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentList;
import envoy.client.ui.list.ComponentList.SelectionMode; import envoy.client.ui.list.ComponentList.SelectionMode;
import envoy.client.ui.list.ComponentListModel; import envoy.client.ui.list.Model;
import envoy.client.ui.primary.PrimaryButton; import envoy.client.ui.primary.PrimaryButton;
import envoy.client.ui.primary.PrimaryScrollPane; import envoy.client.ui.primary.PrimaryScrollPane;
import envoy.client.ui.primary.PrimaryTextArea; import envoy.client.ui.primary.PrimaryTextArea;
import envoy.client.ui.renderer.ContactsSearchRenderer;
import envoy.client.ui.renderer.MessageListRenderer;
import envoy.client.ui.renderer.UserListRenderer; import envoy.client.ui.renderer.UserListRenderer;
import envoy.client.ui.settings.SettingsScreen; import envoy.client.ui.settings.SettingsScreen;
import envoy.data.Message; import envoy.data.Message;
@ -54,8 +51,7 @@ import envoy.util.EnvoyLog;
public class ChatWindow extends JFrame { public class ChatWindow extends JFrame {
/** /**
* This int defines the maximum amount of chars allowed per message. Currently * This integer defines the maximum amount of chars allowed per message.
* set at 200.
* *
* @since Envoy 0.1-beta * @since Envoy 0.1-beta
*/ */
@ -77,7 +73,6 @@ public class ChatWindow extends JFrame {
private JTextPane textPane = new JTextPane(); private JTextPane textPane = new JTextPane();
private PrimaryButton postButton = new PrimaryButton("Post"); private PrimaryButton postButton = new PrimaryButton("Post");
private PrimaryButton settingsButton = new PrimaryButton("Settings"); private PrimaryButton settingsButton = new PrimaryButton("Settings");
@SuppressWarnings("unused")
private JPopupMenu contextMenu; private JPopupMenu contextMenu;
// Contacts Header // Contacts Header
@ -90,9 +85,8 @@ public class ChatWindow extends JFrame {
private final PrimaryButton cancelButton = new PrimaryButton("x"); private final PrimaryButton cancelButton = new PrimaryButton("x");
private final PrimaryTextArea searchField = new PrimaryTextArea(space); private final PrimaryTextArea searchField = new PrimaryTextArea(space);
private final PrimaryScrollPane scrollForPossibleContacts = new PrimaryScrollPane(); private final PrimaryScrollPane scrollForPossibleContacts = new PrimaryScrollPane();
private final ContactsSearchRenderer contactRenderer = new ContactsSearchRenderer(); private final Model<User> contactsModel = new Model<>();
private final ComponentListModel<User> contactsModel = new ComponentListModel<>(); private final ComponentList<User> contactList = new ComponentList<User>().setRenderer(ContactSearchComponent::new);
private final ComponentList<User> contactList = new ComponentList<>(contactRenderer);
private static final Logger logger = EnvoyLog.getLogger(ChatWindow.class); private static final Logger logger = EnvoyLog.getLogger(ChatWindow.class);
@ -151,8 +145,11 @@ public class ChatWindow extends JFrame {
messageList.setBorder(new EmptyBorder(space, space, space, space)); messageList.setBorder(new EmptyBorder(space, space, space, space));
messageList.setSelectionMode(SelectionMode.SINGLE); messageList.setSelectionMode(SelectionMode.SINGLE);
messageList.setSelectionListener((list, comp) -> messageList.setSelectionHandler((message, comp, isSelected) -> {
contextMenu.show(comp, 0, 0)); final var theme = Settings.getInstance().getCurrentTheme();
comp.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor());
contextMenu.show(comp, 0, 0);
});
scrollPane.setViewportView(messageList); scrollPane.setViewportView(messageList);
scrollPane.addComponentListener(new ComponentAdapter() { scrollPane.addComponentListener(new ComponentAdapter() {
@ -399,11 +396,11 @@ public class ChatWindow extends JFrame {
gbc_addContact.gridy = 0; gbc_addContact.gridy = 0;
gbc_addContact.insets = insets; gbc_addContact.insets = insets;
addContact.addActionListener((evt) -> { drawContactSearch(gbc_searchPane); }); addContact.addActionListener(evt -> drawContactSearch(gbc_searchPane));
contactsHeader.add(addContact, gbc_addContact); contactsHeader.add(addContact, gbc_addContact);
applyTheme(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme())); applyTheme(Settings.getInstance().getCurrentTheme());
contentPane.add(contactsHeader, gbc_contactsHeader); contentPane.add(contactsHeader, gbc_contactsHeader);
contentPane.revalidate(); contentPane.revalidate();
@ -648,7 +645,7 @@ public class ChatWindow extends JFrame {
this.localDb = localDb; this.localDb = localDb;
this.writeProxy = writeProxy; this.writeProxy = writeProxy;
messageList.setRenderer(new MessageListRenderer(client.getSender().getId())); messageList.setRenderer((list, message) -> new MessageComponent(list, message, client.getSender().getId()));
// Load users and chats // Load users and chats
new Thread(() -> { new Thread(() -> {

View File

@ -14,11 +14,10 @@ import javax.swing.border.EmptyBorder;
import envoy.client.data.Settings; import envoy.client.data.Settings;
import envoy.client.net.Client; import envoy.client.net.Client;
import envoy.client.ui.Theme; import envoy.client.ui.Theme;
import envoy.client.ui.UserComponent;
import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentList;
import envoy.client.ui.list.ComponentList.SelectionMode; import envoy.client.ui.list.ComponentList.SelectionMode;
import envoy.client.ui.list.ComponentListModel; import envoy.client.ui.list.Model;
import envoy.client.ui.renderer.MessageListRenderer;
import envoy.client.ui.renderer.UserComponentListRenderer;
import envoy.data.Message; import envoy.data.Message;
import envoy.data.User; import envoy.data.User;
@ -36,11 +35,11 @@ public class ContactsChooserDialog extends JDialog {
private static final long serialVersionUID = -5774558118579032256L; private static final long serialVersionUID = -5774558118579032256L;
private ComponentList<User> contactList = new ComponentList<>(new UserComponentListRenderer()); private ComponentList<User> contactList = new ComponentList<User>().setRenderer(UserComponent::new);
private JButton okButton = new JButton("Ok"); private JButton okButton = new JButton("Ok");
private JButton cancelButton = new JButton("Cancel"); private JButton cancelButton = new JButton("Cancel");
private final Theme theme = Settings.getInstance().getTheme(Settings.getInstance().getCurrentTheme()); private final Theme theme = Settings.getInstance().getCurrentTheme();
private final JPanel contentPanel = new JPanel(); private final JPanel contentPanel = new JPanel();
@ -67,11 +66,13 @@ public class ContactsChooserDialog extends JDialog {
dialog.setTitle(title); dialog.setTitle(title);
dialog.setDefaultCloseOperation(DISPOSE_ON_CLOSE); dialog.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
dialog.addCancelButtonActionListener(e -> dialog.dispose()); dialog.addCancelButtonActionListener(e -> dialog.dispose());
dialog.getContentPanel() // dialog.getContentPanel()
.add(new MessageListRenderer(client.getSender().getId()).getListCellComponent(null, message, false), BorderLayout.NORTH); // .add(new
// MessageListRenderer(client.getSender().getId()).getListCellComponent(null,
// message, false), BorderLayout.NORTH);
List<User> results = new ArrayList<>(); List<User> results = new ArrayList<>();
dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelectedElements()); dialog.dispose(); }); dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelectedElements()); dialog.dispose(); });
ComponentListModel<User> contactListModel = dialog.getContactList().getModel(); Model<User> contactListModel = dialog.getContactList().getModel();
client.getContacts().getContacts().forEach(user -> contactListModel.add(user)); client.getContacts().getContacts().forEach(user -> contactListModel.add(user));
dialog.setVisible(true); dialog.setVisible(true);
dialog.repaint(); dialog.repaint();
@ -84,7 +85,8 @@ public class ContactsChooserDialog extends JDialog {
*/ */
private ContactsChooserDialog() { private ContactsChooserDialog() {
contactList.setSelectionMode(SelectionMode.MULTIPLE); contactList.setSelectionMode(SelectionMode.MULTIPLE);
// setBounds(100, 100, 450, 300); contactList
.setSelectionHandler((user, comp, isSelected) -> comp.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()));
setModal(true); setModal(true);
getContentPane().setLayout(new BorderLayout()); getContentPane().setLayout(new BorderLayout());
setBackground(theme.getBackgroundColor()); setBackground(theme.getBackgroundColor());
@ -121,6 +123,4 @@ public class ContactsChooserDialog extends JDialog {
private void addOkButtonActionListener(ActionListener l) { okButton.addActionListener(l); } private void addOkButtonActionListener(ActionListener l) { okButton.addActionListener(l); }
private void addCancelButtonActionListener(ActionListener l) { cancelButton.addActionListener(l); } private void addCancelButtonActionListener(ActionListener l) { cancelButton.addActionListener(l); }
private JPanel getContentPanel() { return contentPanel; }
} }

View File

@ -291,7 +291,7 @@ public class LoginDialog extends JDialog {
} }
private void setTheme() { private void setTheme() {
Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); Theme theme = Settings.getInstance().getCurrentTheme();
// Panels // Panels
contentPanel.setBackground(theme.getBackgroundColor()); contentPanel.setBackground(theme.getBackgroundColor());

View File

@ -5,13 +5,12 @@ import java.awt.event.MouseEvent;
import java.awt.event.MouseListener; import java.awt.event.MouseListener;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.function.BiConsumer;
import javax.swing.*; import javax.swing.*;
/** /**
* Provides a vertical list layout of components provided in a * Provides a vertical list layout of components provided in a
* {@link ComponentListModel}. Similar to {@link javax.swing.JList} but capable * {@link Model}. Similar to {@link javax.swing.JList} but capable
* of rendering {@link JPanel}s.<br> * of rendering {@link JPanel}s.<br>
* <br> * <br>
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
@ -24,11 +23,11 @@ import javax.swing.*;
*/ */
public class ComponentList<E> extends JPanel { public class ComponentList<E> extends JPanel {
private ComponentListModel<E> model; private Model<E> model;
private ComponentListCellRenderer<E> renderer; private Renderer<E> renderer;
private SelectionHandler<E> selectionHandler;
private SelectionMode selectionMode = SelectionMode.NONE; private SelectionMode selectionMode = SelectionMode.NONE;
private Set<Integer> selection = new HashSet<>(); private Set<Integer> selection = new HashSet<>();
private BiConsumer<ComponentList<E>, JComponent> selectionListener;
private static final long serialVersionUID = 1759644503942876737L; private static final long serialVersionUID = 1759644503942876737L;
@ -55,64 +54,20 @@ public class ComponentList<E> extends JPanel {
} }
/** /**
* Initializes a default {@link ComponentList} without a model or a renderer. * Creates an instance of {@link ComponentList}.
* *
* @since Envoy v0.1-beta * @since Envoy v0.3-alpha
*/ */
public ComponentList() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); } public ComponentList() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); }
/**
* Creates an instance of {@link ComponentList}.
*
* @param renderer the list cell renderer used to display elements provided by
* the {@link ComponentListModel}
* @since Envoy v0.3-alpha
*/
public ComponentList(ComponentListCellRenderer<E> renderer) {
this();
setRenderer(renderer);
}
/**
* Creates an instance of {@link ComponentList}.
*
* @param model the list model providing the list elements to render
* @param renderer the list cell renderer used to display elements provided by
* the {@link ComponentListModel}
* @since Envoy v0.3-alpha
*/
public ComponentList(ComponentListModel<E> model, ComponentListCellRenderer<E> renderer) {
this(renderer);
setModel(model);
}
/**
* Sets the list model providing the list elements to render. The rendered
* components will be synchronized with the contents of the new model or removed
* if the new model is {@code null}.
*
* @param model the list model to set
* @since Envoy v0.3-alpha
*/
public void setModel(ComponentListModel<E> model) {
// Remove old model
if (this.model != null) this.model.setComponentList(null);
// Synchronize with new model
this.model = model;
if (model != null) model.setComponentList(this);
synchronizeModel();
}
/** /**
* Removes all child components and then adds all components representing the * Removes all child components and then adds all components representing the
* elements of the {@link ComponentListModel}. * elements of the {@link Model}.
* *
* @since Envoy v0.3-alpha * @since Envoy v0.3-alpha
*/ */
public void synchronizeModel() { public void synchronizeModel() {
if (model != null && renderer != null) { if (model != null) {
removeAll(); removeAll();
model.forEach(this::addElement); model.forEach(this::addElement);
revalidate(); revalidate();
@ -127,10 +82,12 @@ public class ComponentList<E> extends JPanel {
* @since Envoy v0.1-beta * @since Envoy v0.1-beta
*/ */
public void selectElement(int index) { public void selectElement(int index) {
final JComponent element = getComponent(index);
if (selection.contains(index)) { if (selection.contains(index)) {
// Remove selection of element at index // Remove selection of element at index
updateElement(index, false); if (selectionHandler != null) selectionHandler.selectionChanged(model.get(index), element, true);
selection.remove(index); selection.remove(index);
} else { } else {
@ -143,10 +100,7 @@ public class ComponentList<E> extends JPanel {
selection.add(index); selection.add(index);
// Update element // Update element
updateElement(index, true); if (selectionHandler != null) selectionHandler.selectionChanged(model.get(index), element, true);
// Trigger selection listener
if (selectionListener != null) selectionListener.accept(this, (JComponent) getComponents()[index]);
} }
} }
@ -160,49 +114,25 @@ public class ComponentList<E> extends JPanel {
* @since Envoy v0.1-alpha * @since Envoy v0.1-alpha
*/ */
public void clearSelection() { public void clearSelection() {
selection.forEach(i -> updateElement(i, false)); if (selectionHandler != null) selection.forEach(i -> selectionHandler.selectionChanged(model.get(i), getComponent(i), false));
selection.clear(); selection.clear();
} }
/** /**
* Adds an object to the list by rendering it with the current * Adds an object to the list by rendering it with the current
* {@link ComponentListCellRenderer}. * {@link Renderer}.
* *
* @param elem the element to add * @param elem the element to add
* @since Envoy v0.3-alpha * @since Envoy v0.3-alpha
*/ */
void addElement(E elem) { addElement(elem, getComponentCount(), false); } void addElement(E elem) {
/**
* Adds an object to the list by rendering it with the current
* {@link ComponentListRenderer}. If the renderer is {@code null}, no action is
* performed.
*
* @param elem the element to add
* @param index the index at which to add the element
* @param isSelected the selection state of the element
* @since Envoy v0.1-beta
*/
private void addElement(E elem, int index, boolean isSelected) {
if (renderer != null) { if (renderer != null) {
final JComponent component = renderer.getListCellComponent(this, elem, isSelected); final JComponent component = renderer.getListCellComponent(this, elem);
component.addMouseListener(getSelectionListener(index)); component.addMouseListener(getSelectionListener(getComponentCount()));
add(component, index); add(component, getComponentCount());
} }
} }
/**
* Replaces a list element with a newly rendered instance of its contents.
*
* @param index the index of the element to update
* @param isSelected the selection state passed to the {@link ListCellRenderer}
* @since Envoy v0.1-beta
*/
private void updateElement(int index, boolean isSelected) {
remove(index);
addElement(model.get(index), index, isSelected);
}
/** /**
* @param componentIndex the index of the list component to which the mouse * @param componentIndex the index of the list component to which the mouse
* listener will be added * listener will be added
@ -219,6 +149,9 @@ public class ComponentList<E> extends JPanel {
}; };
} }
@Override
public JComponent getComponent(int n) { return (JComponent) super.getComponent(n); }
/** /**
* @return a set of all selected indices * @return a set of all selected indices
* @since Envoy v0.1-beta * @since Envoy v0.1-beta
@ -253,19 +186,45 @@ public class ComponentList<E> extends JPanel {
* @return the model * @return the model
* @since Envoy v0.1-beta * @since Envoy v0.1-beta
*/ */
public ComponentListModel<E> getModel() { return model; } public Model<E> getModel() { return model; }
/**
* Sets the list model providing the list elements to render. The rendered
* components will be synchronized with the contents of the new model or removed
* if the new model is {@code null}.
*
* @param model the list model to set
* @return this component list
* @since Envoy v0.3-alpha
*/
public ComponentList<E> setModel(Model<E> model) {
// Remove old model
if (this.model != null) this.model.setComponentList(null);
// Synchronize with new model
this.model = model;
if (model != null) model.setComponentList(this);
synchronizeModel();
return this;
}
/** /**
* @return the renderer * @return the renderer
* @since Envoy v0.1-beta * @since Envoy v0.1-beta
*/ */
public ComponentListCellRenderer<E> getRenderer() { return renderer; } public Renderer<E> getRenderer() { return renderer; }
/** /**
* @param renderer the renderer to set * @param renderer the renderer to set
* @return this component list
* @since Envoy v0.1-beta * @since Envoy v0.1-beta
*/ */
public void setRenderer(ComponentListCellRenderer<E> renderer) { this.renderer = renderer; } public ComponentList<E> setRenderer(Renderer<E> renderer) {
this.renderer = renderer;
return this;
}
/** /**
* @return the selection mode * @return the selection mode
@ -274,18 +233,28 @@ public class ComponentList<E> extends JPanel {
public SelectionMode getSelectionMode() { return selectionMode; } public SelectionMode getSelectionMode() { return selectionMode; }
/** /**
* Sets a new selection mode. The current selection will be cleared during this
* action.
*
* @param selectionMode the selection mode to set * @param selectionMode the selection mode to set
* @return this component list
* @since Envoy v0.1-beta * @since Envoy v0.1-beta
*/ */
public void setSelectionMode(SelectionMode selectionMode) { this.selectionMode = selectionMode; } public ComponentList<E> setSelectionMode(SelectionMode selectionMode) {
this.selectionMode = selectionMode;
clearSelection();
return this;
}
/** /**
* @return the selectionListener * @return the selection handler
* @since Envoy v0.1-beta
*/ */
public BiConsumer<ComponentList<E>, JComponent> getSelectionListener() { return selectionListener; } public SelectionHandler<E> getSelectionHandler() { return selectionHandler; }
/** /**
* @param selectionListener the selectionListener to set * @param selectionHandler the selection handler to set
* @since Envoy v0.1-beta
*/ */
public void setSelectionListener(BiConsumer<ComponentList<E>, JComponent> selectionListener) { this.selectionListener = selectionListener; } public void setSelectionHandler(SelectionHandler<E> selectionHandler) { this.selectionHandler = selectionHandler; }
} }

View File

@ -9,19 +9,19 @@ import java.util.List;
* Stores objects that will be displayed in a {@link ComponentList}.<br> * Stores objects that will be displayed in a {@link ComponentList}.<br>
* <br> * <br>
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
* File: <strong>ComponentListModel.java</strong><br> * File: <strong>Model.java</strong><br>
* Created: <strong>25.01.2020</strong><br> * Created: <strong>25.01.2020</strong><br>
* *
* @param <E> the type of object displayed in this list * @param <E> the type of object displayed in this list
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy v0.3-alpha
*/ */
public final class ComponentListModel<E> implements Iterable<E>, Serializable { public final class Model<E> implements Iterable<E>, Serializable {
private List<E> elements = new ArrayList<>(); private List<E> elements = new ArrayList<>();
transient private ComponentList<E> componentList; transient private ComponentList<E> componentList;
private static final long serialVersionUID = 4815005915255497331L; private static final long serialVersionUID = 0L;
/** /**
* Adds an element to this model and notifies the associated * Adds an element to this model and notifies the associated

View File

@ -7,14 +7,15 @@ import javax.swing.JComponent;
* that can be rendered.<br> * that can be rendered.<br>
* <br> * <br>
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
* File: <strong>ComponentListCellRenderer.java</strong><br> * File: <strong>Renderer.java</strong><br>
* Created: <strong>25.01.2020</strong><br> * Created: <strong>25.01.2020</strong><br>
* *
* @param <E> the type of object displayed in this list * @param <E> the type of object displayed in this list
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy v0.3-alpha
*/ */
public interface ComponentListCellRenderer<E> { @FunctionalInterface
public interface Renderer<E> {
/** /**
* Provides a Swing component representing a list element. * Provides a Swing component representing a list element.
@ -26,5 +27,5 @@ public interface ComponentListCellRenderer<E> {
* @return the component representing the list element * @return the component representing the list element
* @since Envoy v0.3-alpha * @since Envoy v0.3-alpha
*/ */
JComponent getListCellComponent(ComponentList<? extends E> list, E value, boolean isSelected); JComponent getListCellComponent(ComponentList<? extends E> list, E value);
} }

View File

@ -0,0 +1,27 @@
package envoy.client.ui.list;
import javax.swing.JComponent;
/**
* Handles the selection of elements in a {@link ComponentList}.<br>
* <br>
* Project: <strong>envoy-client</strong>
* File: <strong>SelectionHandler.java</strong>
* Created: <strong>21.03.2020</strong>
*
* @author Kai S. K. Engelbart
* @since Envoy v0.1-beta
*/
@FunctionalInterface
public interface SelectionHandler<E> {
/**
* Notifies the handler about a selection.
*
* @param element the selected element
* @param component the selected component
* @param isSelected contains the selection state
* @since Envoy v0.1-beta
*/
void selectionChanged(E element, JComponent component, boolean isSelected);
}

View File

@ -1,11 +1,6 @@
package envoy.client.ui.primary; package envoy.client.ui.primary;
import java.awt.Color; import java.awt.*;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComponent; import javax.swing.JComponent;
@ -98,11 +93,11 @@ public class PrimaryScrollBar extends BasicScrollBarUI {
g2.setPaint(color); g2.setPaint(color);
if (isVertical) { if (isVertical) {
g2.fillRoundRect(r.x - 9, r.y, r.width, r.height, arcSize, arcSize); g2.fillRoundRect(r.x - 9, r.y, r.width, r.height, arcSize, arcSize);
g2.setPaint(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getCellColor()); g2.setPaint(Settings.getInstance().getCurrentTheme().getCellColor());
g2.drawRoundRect(r.x - 9, r.y, r.width, r.height, arcSize, arcSize); g2.drawRoundRect(r.x - 9, r.y, r.width, r.height, arcSize, arcSize);
} else { } else {
g2.fillRoundRect(r.x, r.y + 9, r.width, r.height - 10, arcSize, arcSize); g2.fillRoundRect(r.x, r.y + 9, r.width, r.height - 10, arcSize, arcSize);
g2.setPaint(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getCellColor()); g2.setPaint(Settings.getInstance().getCurrentTheme().getCellColor());
g2.drawRoundRect(r.x, r.y + 9, r.width, r.height - 10, arcSize, arcSize); g2.drawRoundRect(r.x, r.y + 9, r.width, r.height - 10, arcSize, arcSize);
} }
g2.dispose(); g2.dispose();

View File

@ -52,7 +52,7 @@ public class PrimaryToggleSwitch extends JButton {
g.setColor(state ? Color.GREEN : Color.LIGHT_GRAY); g.setColor(state ? Color.GREEN : Color.LIGHT_GRAY);
g.fillRoundRect(0, 0, getWidth(), getHeight(), 25, 25); g.fillRoundRect(0, 0, getWidth(), getHeight(), 25, 25);
g.setColor(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getInteractableBackgroundColor()); g.setColor(Settings.getInstance().getCurrentTheme().getInteractableBackgroundColor());
g.fillRoundRect(state ? 25 : 0, 0, 25, 25, 25, 25); g.fillRoundRect(state ? 25 : 0, 0, 25, 25, 25, 25);
} }
} }

View File

@ -1,42 +0,0 @@
package envoy.client.ui.renderer;
import javax.swing.JPanel;
import envoy.client.ui.MessageComponent;
import envoy.client.ui.list.ComponentList;
import envoy.client.ui.list.ComponentListCellRenderer;
import envoy.data.Message;
/**
* Defines how a message is displayed.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageListRenderer.java</strong><br>
* Created: <strong>19 Oct 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @author Leon Hofmeister
* @since Envoy v0.1-alpha
*/
public class MessageListRenderer implements ComponentListCellRenderer<Message> {
private final long senderId;
/**
* Initializes a message list renderer. Messages with the given sender ID will
* be aligned on the right side, while all other messages will be aligned on
* the left side
*
* @param senderId the sender ID of the messages to align on the right side
* @since Envoy v0.1-beta
*/
public MessageListRenderer(long senderId) { this.senderId = senderId; }
// TODO: Handle message attachments
@Override
public JPanel getListCellComponent(ComponentList<? extends Message> list, Message message, boolean isSelected) {
return new MessageComponent(list, message, isSelected, senderId);
}
}

View File

@ -1,70 +0,0 @@
package envoy.client.ui.renderer;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.io.Serializable;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import envoy.client.data.Settings;
import envoy.client.ui.Color;
import envoy.client.ui.Theme;
import envoy.client.ui.list.ComponentList;
import envoy.client.ui.list.ComponentListCellRenderer;
import envoy.data.User;
import envoy.data.User.UserStatus;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserComponentListRenderer.java</strong><br>
* Created: <strong>15 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy v0.1-beta
*/
public class UserComponentListRenderer implements ComponentListCellRenderer<User>, Serializable {
private static final long serialVersionUID = -2379244319112111284L;
@Override
public JComponent getListCellComponent(ComponentList<? extends User> list, User user, boolean isSelected) {
final Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme());
final JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
// Panel background
panel.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor());
panel.setOpaque(true);
panel.setPreferredSize(new Dimension(100, 35));
// TODO add profile picture support in BorderLayout.West
JLabel username = new JLabel(user.getName());
username.setForeground(theme.getUserNameColor());
panel.add(username, BorderLayout.CENTER);
final UserStatus status = user.getStatus();
JLabel statusLabel = new JLabel(status.toString());
Color foreground;
switch (status) {
case AWAY:
foreground = Color.yellow;
break;
case BUSY:
foreground = Color.blue;
break;
case ONLINE:
foreground = Color.green;
break;
default:
foreground = Color.lightGray;
break;
}
statusLabel.setForeground(foreground);
panel.add(statusLabel, BorderLayout.NORTH);
return panel;
}
}

View File

@ -46,7 +46,7 @@ public class UserListRenderer extends JLabel implements ListCellRenderer<User> {
// Getting the UserNameColor of the current theme // Getting the UserNameColor of the current theme
String textColor = null; String textColor = null;
textColor = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getUserNameColor().toHex(); textColor = Settings.getInstance().getCurrentTheme().getUserNameColor().toHex();
switch (status) { switch (status) {
case ONLINE: case ONLINE:
setText(String setText(String

View File

@ -43,7 +43,7 @@ public class GeneralSettingsPanel extends SettingsPanel {
*/ */
public GeneralSettingsPanel(SettingsScreen parent) { public GeneralSettingsPanel(SettingsScreen parent) {
super(parent); super(parent);
theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); theme = Settings.getInstance().getCurrentTheme();
setBackground(theme.getCellColor()); setBackground(theme.getCellColor());

View File

@ -61,7 +61,7 @@ public class NewThemeScreen extends JDialog {
setDimensions(true); setDimensions(true);
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); Theme theme = Settings.getInstance().getCurrentTheme();
getContentPane().setLayout(new BorderLayout()); getContentPane().setLayout(new BorderLayout());
standardPanel.setBackground(theme.getBackgroundColor()); standardPanel.setBackground(theme.getBackgroundColor());

View File

@ -156,7 +156,7 @@ public class SettingsScreen extends JDialog {
} }
// Apply current theme // Apply current theme
applyTheme(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme())); applyTheme(Settings.getInstance().getCurrentTheme());
// Respond to theme changes // Respond to theme changes
EventBus.getInstance().register(ThemeChangeEvent.class, evt -> applyTheme(evt.get())); EventBus.getInstance().register(ThemeChangeEvent.class, evt -> applyTheme(evt.get()));

View File

@ -38,6 +38,7 @@ public class ThemeCustomizationPanel extends SettingsPanel {
private final Insets insets = new Insets(5, 5, 5, 5); private final Insets insets = new Insets(5, 5, 5, 5);
private static final Settings settings = Settings.getInstance();
private static final Logger logger = EnvoyLog.getLogger(ThemeCustomizationPanel.class); private static final Logger logger = EnvoyLog.getLogger(ThemeCustomizationPanel.class);
private static final long serialVersionUID = -8697897390666456624L; private static final long serialVersionUID = -8697897390666456624L;
@ -52,7 +53,7 @@ public class ThemeCustomizationPanel extends SettingsPanel {
*/ */
public ThemeCustomizationPanel(SettingsScreen parent) { public ThemeCustomizationPanel(SettingsScreen parent) {
super(parent); super(parent);
temporaryTheme = new Theme("temporaryTheme", Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme())); temporaryTheme = new Theme("temporaryTheme", settings.getCurrentTheme());
GridBagLayout gbl_themeLayout = new GridBagLayout(); GridBagLayout gbl_themeLayout = new GridBagLayout();
@ -63,7 +64,7 @@ public class ThemeCustomizationPanel extends SettingsPanel {
setLayout(gbl_themeLayout); setLayout(gbl_themeLayout);
themes.setSelectedItem(Settings.getInstance().getCurrentTheme()); themes.setSelectedItem(settings.getCurrentTheme());
GridBagConstraints gbc_themes = new GridBagConstraints(); GridBagConstraints gbc_themes = new GridBagConstraints();
gbc_themes.fill = GridBagConstraints.HORIZONTAL; gbc_themes.fill = GridBagConstraints.HORIZONTAL;
@ -83,7 +84,7 @@ public class ThemeCustomizationPanel extends SettingsPanel {
colorsPanel.setLayout(gbl_colorCustomizations); colorsPanel.setLayout(gbl_colorCustomizations);
Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); Theme theme = settings.getCurrentTheme();
buildCustomizeElements(theme); buildCustomizeElements(theme);
GridBagConstraints gbc_colorsPanel = new GridBagConstraints(); GridBagConstraints gbc_colorsPanel = new GridBagConstraints();