Skip to content

Commit

Permalink
Add an image viewer.
Browse files Browse the repository at this point in the history
  • Loading branch information
io7m committed Jan 24, 2024
1 parent 8b7787e commit 2b1befb
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ public final class LCaptionsView implements LScreenViewType
@FXML private TextField captionAvailableSearch;
@FXML private TextField imageSearch;

private Stage imageDisplayWindow;
private LImageView imageDisplay;

/**
* The captions view.
*
Expand Down Expand Up @@ -116,6 +119,14 @@ public void initialize(
final URL url,
final ResourceBundle resourceBundle)
{
this.imageDisplayWindow = new Stage();
this.imageDisplay =
LImageView.create(
this.imageDisplayWindow,
this.services,
this.strings
);

this.captions.setDisable(true);
this.captions.setVisible(false);

Expand Down Expand Up @@ -340,8 +351,8 @@ private void onImageSelected(
imageFileOpt.get().toUri().toString(),
256.0,
256.0,
false,
false,
true,
true,
true
);

Expand Down Expand Up @@ -548,4 +559,12 @@ private void onCaptionGlobal()
throw new UncheckedIOException(e);
}
}

@FXML
private void onImageClicked()
{
if (!this.imageDisplayWindow.isShowing()) {
this.imageDisplayWindow.show();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,12 @@ public void imagesDelete(
);
}

@Override
public ReadOnlyProperty<LMImage> imageSelected()
{
return this.model.imageSelected();
}

@Override
public ReadOnlyProperty<LModelFileStatusType> fileStatus()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,4 +311,10 @@ void globalPrefixCaptionModify(

void imagesDelete(
List<LMImage> images);

/**
* @return The selected image
*/

ReadOnlyProperty<LMImage> imageSelected();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* Copyright © 2024 Mark Raynsford <[email protected]> https://www.io7m.com
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/


package com.io7m.laurel.gui.internal;

import com.io7m.laurel.gui.internal.model.LMImage;
import com.io7m.repetoir.core.RPServiceDirectoryType;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle;

/**
* The About screen.
*/

public final class LImageView implements LScreenViewType
{
private final Stage stage;
private final LControllerType controller;

private @FXML Rectangle border;
private @FXML ImageView imageView;
private @FXML Button dismiss;
private @FXML ProgressBar imageProgress;

/**
* The image view screen.
*
* @param services The services
* @param inStage The stage
*/

public LImageView(
final RPServiceDirectoryType services,
final Stage inStage)
{
this.controller =
services.requireService(LControllerType.class);
this.stage =
Objects.requireNonNull(inStage, "inStage");
}

@Override
public void initialize(
final URL url,
final ResourceBundle resourceBundle)
{
this.border.widthProperty()
.bind(this.stage.widthProperty().subtract(24.0));
this.border.heightProperty()
.bind(this.stage.heightProperty().subtract(32 + 32 + 16 + 8));

this.border.widthProperty()
.addListener((observable, oldValue, newValue) -> {
this.imageView.setFitWidth(newValue.doubleValue() - 2.0);
});
this.border.heightProperty()
.addListener((observable, oldValue, newValue) -> {
this.imageView.setFitHeight(newValue.doubleValue() - 2.0);
});

this.controller.imageSelected()
.subscribe((image1, image2) -> {
this.onImageSelected(image2);
});
}

private void onImageSelected(
final LMImage image)
{
if (image == null) {
this.controller.imageSelect(Optional.empty());
this.imageView.setImage(null);
return;
}

final var imageFileOpt =
Optional.ofNullable(image.fileName().get());

this.imageProgress.setVisible(true);
if (imageFileOpt.isPresent()) {
final var imageValue =
new Image(
imageFileOpt.get().toUri().toString(),
this.imageView.getFitWidth(),
this.imageView.getFitHeight(),
false,
true,
true
);

this.imageProgress.setVisible(true);

imageValue.exceptionProperty()
.subscribe(e -> {
this.imageProgress.setVisible(true);
});

imageValue.progressProperty()
.addListener((observable, oldValue, newValue) -> {
if (newValue.doubleValue() >= 1.0) {
this.imageProgress.setVisible(false);
}
});

this.imageProgress.progressProperty()
.bind(imageValue.progressProperty());

this.imageView.setImage(imageValue);
} else {
this.imageView.setImage(null);
this.imageProgress.setVisible(false);
}
}

@FXML
private void onDismiss()
{
this.stage.close();
}

/**
* The image view screen.
*
* @param services The services
* @param strings The strings
*
* @return The about screen
*/

public static LImageView create(
final Stage stage,
final RPServiceDirectoryType services,
final LStrings strings)
{
try {
final var layout =
LExporterDialogs.class.getResource(
"/com/io7m/laurel/gui/internal/image.fxml");

Objects.requireNonNull(layout, "layout");

final var loader =
new FXMLLoader(layout, strings.resources());

final var image = new LImageView(services, stage);
loader.setControllerFactory(param -> {
return image;
});

final Pane pane = loader.load();
LCSS.setCSS(pane);

stage.setTitle(strings.format("image"));
stage.setWidth(512);
stage.setMinWidth(256);
stage.setMinHeight(256);
stage.setHeight(512);
stage.setScene(new Scene(pane));
return image;
} catch (final IOException e) {
throw new UncheckedIOException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ caption.text=Caption
captions.tooltip.add=Add new caption.
captions.tooltip.add_to_image=Add the selected caption to the current image.
captions.tooltip.delete=Delete the selected caption.
captions.tooltip.global=Configure global prefix captions.
captions.tooltip.priority_down=Reduce caption priority.
captions.tooltip.priority_up=Increase caption priority.
captions.tooltip.remove_from_image=Remove the selected caption from the current image.
captions.tooltip.global=Configure global prefix captions.
confirm_unsaved=There is unsaved data. Do you want to save before continuing?
discard=Discard
error.dismiss=Dismiss
Expand All @@ -38,11 +38,13 @@ export.directory=Directory
export.include_images=Export Images
export.select=Select...
export=Export
globals=Global prefix captions
image=Image
images.filename=Filename
images.search_by_caption=Search by caption...
images.tooltip.search=Search for images with the given comma-separated list of captions.
images.tooltip.add=Add a new image.
images.tooltip.delete=Delete the selected image.
images.tooltip.search=Search for images with the given comma-separated list of captions.
import.select=Select a directory filled with captioned images...
placeholder=Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Euismod nisi porta lorem mollis aliquam. In cursus turpis massa tincidunt. Felis eget velit aliquet sagittis id consectetur purus ut. Felis eget velit aliquet sagittis id consectetur purus. Lacus sed turpis tincidunt id aliquet risus feugiat in ante. Orci nulla pellentesque dignissim enim. Urna porttitor rhoncus dolor purus non enim praesent elementum facilisis. Tortor aliquam nulla facilisi cras. Feugiat pretium nibh ipsum consequat. Mattis molestie a iaculis at erat pellentesque adipiscing commodo. Sagittis vitae et leo duis. Vitae et leo duis ut diam quam nulla. Ut ornare lectus sit amet est placerat in. Nec sagittis aliquam malesuada bibendum arcu vitae elementum. Duis convallis convallis tellus id interdum velit laoreet id donec. Rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt.
redo=Redo
Expand All @@ -55,4 +57,3 @@ title.unsaved=Laurel ({0}) *
title=Laurel
undo=Undo
undo_specific=Undo ({0})
globals=Global prefix captions
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<StackPane>
<children>
<Rectangle arcHeight="1.0" arcWidth="1.0" fill="#ffffff00" height="258.0" stroke="#979797" strokeType="INSIDE" width="258.0" />
<ImageView fx:id="imageView" fitHeight="256.0" fitWidth="256.0" pickOnBounds="true" preserveRatio="true" StackPane.alignment="CENTER" />
<ImageView fx:id="imageView" fitHeight="256.0" fitWidth="256.0" onMouseReleased="#onImageClicked" pickOnBounds="true" preserveRatio="true" StackPane.alignment="CENTER" />
<ProgressBar fx:id="imageProgress" prefHeight="8.0" prefWidth="128.0" />
<HBox fx:id="errorImageLoad" maxHeight="-Infinity" minHeight="-Infinity" prefHeight="32.0" StackPane.alignment="BOTTOM_RIGHT">
<StackPane.margin>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.shape.Rectangle?>

<AnchorPane xmlns="http://javafx.com/javafx/21.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.io7m.laurel.gui.internal.LImageView">
<children>
<Rectangle fx:id="border" arcHeight="5.0" arcWidth="5.0" fill="#1f93ff00" height="512.0" stroke="WHITE" strokeType="INSIDE" width="512.0" AnchorPane.bottomAnchor="47.0" AnchorPane.leftAnchor="7.0" AnchorPane.rightAnchor="7.0" AnchorPane.topAnchor="7.0" />
<ImageView fx:id="imageView" fitHeight="512.0" fitWidth="512.0" layoutX="8.0" layoutY="8.0" pickOnBounds="true" preserveRatio="true" AnchorPane.bottomAnchor="48.0" AnchorPane.leftAnchor="8.0" AnchorPane.rightAnchor="8.0" AnchorPane.topAnchor="8.0" />
<ProgressBar fx:id="imageProgress" prefHeight="16.0" prefWidth="128.0" progress="0.0" AnchorPane.bottomAnchor="64.0" AnchorPane.leftAnchor="128.0" AnchorPane.rightAnchor="128.0" />
<HBox alignment="CENTER" layoutX="8.0" layoutY="528.0" maxHeight="-Infinity" minHeight="-Infinity" prefHeight="32.0" AnchorPane.bottomAnchor="8.0" AnchorPane.leftAnchor="8.0" AnchorPane.rightAnchor="8.0">
<children>
<Region HBox.hgrow="ALWAYS" />
<Button fx:id="dismiss" defaultButton="true" mnemonicParsing="false" onAction="#onDismiss" prefHeight="32.0" prefWidth="128.0" text="%error.dismiss" HBox.hgrow="NEVER">
<HBox.margin>
<Insets />
</HBox.margin>
</Button>
</children>
</HBox>
</children>
</AnchorPane>

0 comments on commit 2b1befb

Please sign in to comment.