diff --git a/CanadaDatamartProvider.jar b/CanadaDatamartProvider.jar index b4a6838..2c9f0d6 100644 Binary files a/CanadaDatamartProvider.jar and b/CanadaDatamartProvider.jar differ diff --git a/CanadaDatamartProvider.properties b/CanadaDatamartProvider.properties index 0da62c2..d0ca5fa 100644 --- a/CanadaDatamartProvider.properties +++ b/CanadaDatamartProvider.properties @@ -1,4 +1,4 @@ #VisualForecast 1000 Properties file. Functional provider must be set for successful boot! -#Thu Mar 07 16:27:03 PST 2024 +#Fri Mar 15 19:46:22 PDT 2024 towns-by-code= -towns-by-name-and-province=Vancouver,BC;Kamloops,BC;Kelowna,BC +towns-by-name-and-province=Vancouver,BC;Victora,BC;Kelowna,BC;Nanaimo,BC;Squamish,BC;Prince George,BC;Prince Rupert,BC diff --git a/MockForecastProvider.jar b/MockForecastProvider.jar index d6144df..ebbf2ae 100644 Binary files a/MockForecastProvider.jar and b/MockForecastProvider.jar differ diff --git a/src/com/flaremicro/visualforecast/RenderPanel.java b/src/com/flaremicro/visualforecast/RenderPanel.java index 37ecee8..934bf69 100644 --- a/src/com/flaremicro/visualforecast/RenderPanel.java +++ b/src/com/flaremicro/visualforecast/RenderPanel.java @@ -14,7 +14,6 @@ import java.awt.image.BufferedImage; import java.awt.image.VolatileImage; import java.io.BufferedReader; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; @@ -29,6 +28,9 @@ import com.flaremicro.visualforecast.api.ForecastProvider; import com.flaremicro.visualforecast.displays.BootupDisplay; import com.flaremicro.visualforecast.displays.DayForecastDisplay; import com.flaremicro.visualforecast.displays.Display; +import com.flaremicro.visualforecast.displays.DisplayFactory; +import com.flaremicro.visualforecast.displays.HourlyForecastDisplay; +import com.flaremicro.visualforecast.displays.ThirtySixHourDisplay; import com.flaremicro.visualforecast.forecast.ForecastDetails; import com.flaremicro.visualforecast.graphics.DrawingUtil; import com.flaremicro.visualforecast.graphics.FontManager; @@ -54,6 +56,8 @@ public class RenderPanel extends JPanel implements Tickable, ComponentListener { private Rectangle exclusiveRedrawBound = null; private Rectangle crawlBound = null; + private Rectangle currentBound = new Rectangle(0, 0, 0, 0); + private String currentTown = ""; private String currentForecast = ""; @@ -66,6 +70,8 @@ public class RenderPanel extends JPanel implements Tickable, ComponentListener { private int crawlPosition = 0; private int stringIndex = -1; private int currentCrawlStringWidth = 0; + + DisplayFactory displayFactory; public RenderPanel(PropertyManager propManager) { loadCrawlStrings(); @@ -74,6 +80,7 @@ public class RenderPanel extends JPanel implements Tickable, ComponentListener { this.propManager = propManager; font = FontManager.getInstance().getOrCreateFont(Font.TRUETYPE_FONT, this.getClass().getResource("/Star4000.ttf")); currentFlavour.initDisplay(this, null, ticks, iconAnimationTicks); + displayFactory = new DisplayFactory(propManager); //new Thread(new TickThread(this, 30)).start(); } @@ -109,30 +116,8 @@ public class RenderPanel extends JPanel implements Tickable, ComponentListener { public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = prepareFrameBuffer(); - - - if(g.getClipBounds().equals(this.crawlBound)) - { - g2d.setColor(BG_BLUE); - g2d.fillRect(0, H - INFOBAR_HEIGHT, W, INFOBAR_HEIGHT); - - g2d.setColor(Color.DARK_GRAY); - g2d.drawLine(0, H - INFOBAR_HEIGHT + STROKE_OFFSET, W, H - INFOBAR_HEIGHT + STROKE_OFFSET); - - g2d.setColor(Color.WHITE); - g2d.drawLine(0, H - INFOBAR_HEIGHT + STROKE_WIDTH + STROKE_OFFSET, W, H - INFOBAR_HEIGHT + STROKE_WIDTH + STROKE_OFFSET); - if (this.currentCrawlString != null) - { - g2d.setFont(font.deriveFont(26F)); - DrawingUtil.drawOutlinedString(g2d, this.crawlPosition, H - INFOBAR_HEIGHT + 30, this.currentCrawlString, Color.WHITE, Color.BLACK, 2); - } - g2d.dispose(); - g.drawImage(frameBuffer, 0, 0, getWidth(), getHeight(), this); - return; - } - drawMainRegion(g2d); - + if (currentFlavour != null) { if (this.getBounds().equals(g.getClipBounds())) @@ -152,7 +137,7 @@ public class RenderPanel extends JPanel implements Tickable, ComponentListener { g2d.setColor(BG_PURPLE); g2d.fillRect(0, 0, W, TOPBAR_HEIGHT); - g2d.setPaint(new GradientPaint(0, HEADERBAR_Y, BG_OORANGE, 0, HEADERBAR_Y + (HEADERBAR_HEIGHT + 10), BG_PURPLE)); + g2d.setPaint(new GradientPaint(0, HEADERBAR_Y, BG_ORANGE, 0, HEADERBAR_Y + (HEADERBAR_HEIGHT + 10), BG_PURPLE)); g2d.shear(HEADERBAR_SHEAR, 0); g2d.fillRect(-HEADERBAR_OFFSET, HEADERBAR_Y, HEADERBAR_WIDTH, HEADERBAR_HEIGHT); @@ -160,7 +145,7 @@ public class RenderPanel extends JPanel implements Tickable, ComponentListener { g2d.fillRect(W - TIMEBAR_WIDTH + TIMEBAR_OFFSET, TIMEBAR_Y, TIMEBAR_WIDTH, TIMEBAR_HEIGHT); g2d.shear(-HEADERBAR_SHEAR, 0); - g2d.setPaint(new GradientPaint(0, TOPBAR_HEIGHT, BG_PURPLE, 0, MAINBAR_HEIGHT, BG_OORANGE)); + g2d.setPaint(new GradientPaint(0, TOPBAR_HEIGHT, BG_PURPLE, 0, MAINBAR_HEIGHT, BG_ORANGE)); g2d.fillRect(0, TOPBAR_HEIGHT, W, MAINBAR_HEIGHT); g2d.fillRect(0, TOPBAR_HEIGHT, W, MAINBAR_HEIGHT); @@ -177,7 +162,7 @@ public class RenderPanel extends JPanel implements Tickable, ComponentListener { DrawingUtil.drawOutlinedString(g2d, W - sw - 60, TIMEBAR_Y + 36, timeString, Color.WHITE, Color.BLACK, 2); g2d.setFont(font.deriveFont(36F)); DrawingUtil.drawOutlinedString(g2d, 60, HEADERBAR_Y + 52, currentTown, Color.YELLOW, Color.BLACK, 2); - + g2d.setColor(BG_BLUE); g2d.fillRect(0, H - INFOBAR_HEIGHT, W, INFOBAR_HEIGHT); @@ -202,7 +187,7 @@ public class RenderPanel extends JPanel implements Tickable, ComponentListener { //Clock and icon animations, should use repaint regions later if (getWidth() > 0 && getHeight() > 0) { - repaint(this.redrawBound); + requestRepaint(this.redrawBound); } } if (this.currentFlavour != null) @@ -218,10 +203,37 @@ public class RenderPanel extends JPanel implements Tickable, ComponentListener { this.currentCrawlStringWidth = g.getFontMetrics(font.deriveFont(26F)).stringWidth(currentCrawlString); g.dispose(); } - else if (this.currentCrawlString != null) + } + + public void requestFullRepaint() { + currentBound = this.getBounds(); + } + + + public void requestRepaint(Rectangle bound) { + if(currentBound.width == 0) + currentBound = new Rectangle(bound); + else currentBound.add(bound); + } + + public void requestBoundedRepaint() { + requestRepaint(this.redrawBound); + } + + public void requestExclusiveBoundedRepaint() { + requestRepaint(exclusiveRedrawBound); + } + + public void performPaintIfNeeded() { + if (this.currentCrawlString != null) { - this.crawlPosition-=2; - repaint(0, this.crawlBound.x, this.crawlBound.y, this.crawlBound.width, this.crawlBound.height); + this.crawlPosition -= 2; + requestRepaint(crawlBound); + } + if(this.currentBound.width != 0) + { + repaint(currentBound); + currentBound.width = 0; } } @@ -293,7 +305,13 @@ public class RenderPanel extends JPanel implements Tickable, ComponentListener { } public void nextDisplay() { - this.currentFlavour = new DayForecastDisplay(); + /*if(this.currentFlavour instanceof DayForecastDisplay) + this.currentFlavour = new HourlyForecastDisplay(); + else + this.currentFlavour = new DayForecastDisplay();*/ + this.currentFlavour = displayFactory.nextDisplay(); + /*this.currentFlavour = new ThirtySixHourDisplay(); + */ this.currentFlavour.initDisplay(this, forecastProvider, ticks, iconAnimationTicks); this.loseRedrawRegion(); this.requestFullRepaint(); @@ -307,18 +325,6 @@ public class RenderPanel extends JPanel implements Tickable, ComponentListener { this.currentForecast = currentForecast; } - public void requestFullRepaint() { - repaint(); - } - - public void requestBoundedRepaint() { - repaint(redrawBound); - } - - public void requestExclusiveBoundedRepaint() { - repaint(exclusiveRedrawBound); - } - private void loadCrawlStrings() { File crawl = new File("./crawl.txt"); ArrayList strings = new ArrayList(); diff --git a/src/com/flaremicro/visualforecast/VisualForecastFrame.java b/src/com/flaremicro/visualforecast/VisualForecastFrame.java index a12df52..68092f5 100644 --- a/src/com/flaremicro/visualforecast/VisualForecastFrame.java +++ b/src/com/flaremicro/visualforecast/VisualForecastFrame.java @@ -1,15 +1,22 @@ package com.flaremicro.visualforecast; -import java.awt.BorderLayout; import java.awt.EventQueue; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.io.File; +import java.util.Map; import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.Timer; +import com.flaremicro.util.Util; import com.flaremicro.visualforecast.api.ForecastProvider; import com.flaremicro.visualforecast.api.ForecastProviderManager; @@ -23,12 +30,15 @@ public class VisualForecastFrame extends JFrame implements WindowListener, KeyLi private ForecastProviderManager forecastProviderManager; private Executor executor; private PropertyManager propertyManager = new PropertyManager(); + private Timer timer; + private Timer timer2; private boolean isFullscreen = false; /** * Launch the application. */ public static void main(String[] args) { + final Map arguments = Util.parseArgs(args, true); EventQueue.invokeLater(new Runnable() { public void run() { try @@ -36,8 +46,31 @@ public class VisualForecastFrame extends JFrame implements WindowListener, KeyLi VisualForecastFrame frame = new VisualForecastFrame(); frame.setVisible(true); frame.init(); + + if (arguments.containsKey("fullscreen")) + { + try + { + + int index = Integer.parseInt(arguments.get("fullscreen")); + + GraphicsDevice[] devices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices(); + if(index < 0 || devices.length < index) + { + JOptionPane.showMessageDialog(frame, arguments.get("fullscreen") + " does not exist as a screen index"); + } + else + { + devices[index].setFullScreenWindow(frame); + } + } + catch (NumberFormatException ex) + { + JOptionPane.showMessageDialog(frame, arguments.get("fullscreen") + " is not a valid screen index"); + } + } //GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[1].setFullScreenWindow(frame); - frame.createBufferStrategy(2); + //frame.createBufferStrategy(2); } catch (Exception e) { @@ -50,6 +83,10 @@ public class VisualForecastFrame extends JFrame implements WindowListener, KeyLi public void end() { if (executor != null) executor.end(); + if (timer != null) + { + timer.stop(); + } if (forecastProviderManager != null) forecastProviderManager.end(); propertyManager.store(); @@ -58,6 +95,13 @@ public class VisualForecastFrame extends JFrame implements WindowListener, KeyLi public void init() { executor = new Executor(this.renderPane, 30); executor.begin(); + timer = new Timer(33, new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + renderPane.performPaintIfNeeded(); + } + }); + timer.start(); new Thread() { public void run() { @@ -67,6 +111,7 @@ public class VisualForecastFrame extends JFrame implements WindowListener, KeyLi propertyManager.store(); ForecastProvider provider = forecastProviderManager.loadProvider(new File(forecastProvider)); renderPane.setForecastProvider(provider); + } }.start(); @@ -79,9 +124,9 @@ public class VisualForecastFrame extends JFrame implements WindowListener, KeyLi setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); setBounds(100, 100, 640 * 2, 480 * 2); renderPane = new RenderPanel(propertyManager); - renderPane.setBorder(null); - renderPane.setLayout(new BorderLayout(0, 0)); - setContentPane(renderPane); + //renderPane.setBorder(null); + //renderPane.setLayout(new BorderLayout(0, 0)); + add(renderPane); setUndecorated(true); addWindowListener(this); addKeyListener(this); diff --git a/src/com/flaremicro/visualforecast/displays/CurrentForecastDisplay.java b/src/com/flaremicro/visualforecast/displays/CurrentForecastDisplay.java new file mode 100644 index 0000000..7dd42cd --- /dev/null +++ b/src/com/flaremicro/visualforecast/displays/CurrentForecastDisplay.java @@ -0,0 +1,175 @@ +package com.flaremicro.visualforecast.displays; + +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.util.ArrayList; + +import com.flaremicro.visualforecast.RenderPanel; +import com.flaremicro.visualforecast.api.ForecastProvider; +import com.flaremicro.visualforecast.graphics.DrawingUtil; +import com.flaremicro.visualforecast.graphics.FontManager; + +import static com.flaremicro.visualforecast.graphics.RenderConstants.*; + +public class CurrentForecastDisplay implements Display { + + private String[] lines = new String[0]; + private Font font; + + private int stringOffset = 0; + private int ticksBeforeChange = 0; + + public CurrentForecastDisplay() { + font = FontManager.getInstance().getOrCreateFont(Font.TRUETYPE_FONT, this.getClass().getResource("/Star4000.ttf")).deriveFont(30F); + + /* + int w2 = g2d.getFontMetrics().stringWidth("EXTREME WEATHER ADVISORY"); + drawOutlinedString(g2d, (W >> 1) - (w2 >> 1), TOPBAR_HEIGHT + 48, "EXTREME WEATHER ADVISORY", Color.RED, Color.BLACK, 2); + + g2d.setFont(font.deriveFont(30F)); + for (int i = 0; i < testString.length; i++) + { + drawOutlinedString(g2d, 90, TOPBAR_HEIGHT + 78 + 25 * i, testString[i], Color.WHITE, Color.BLACK, 1); + } + */ + } + + @Override + public void tick(RenderPanel renderer, long ticks, int iconTicks) { + ticksBeforeChange--; + if (ticksBeforeChange <= 0) + { + stringOffset += 9; + if (stringOffset < lines.length) + { + ticksBeforeChange = 200; + renderer.requestExclusiveBoundedRepaint(); + } + else + { + renderer.nextDisplay(); + } + } + } + + @Override + public void initDisplay(RenderPanel renderer, ForecastProvider forecastProvider, long ticks, int iconTicks) { + ticksBeforeChange = 200; + stringOffset = 0; + String test = "*IMPORTANT NOTICE*\n*THIS BETA IS UNFINISHED*\n\nThis is a Beta version of the VisualForeast 1000! Many things are not finished, and only the hourly and 7 day forecasts are ready. There will be a lot more to come!\nPlease note that there are some issues with data collection from Environment Canada resulting in missing or potentially incorrect information around midnight hours. This will be fixed as the project continues and the disclaimer will be removed."; + ArrayList linesList = new ArrayList(); + + renderer.setCurrentForecast("Welcome to the"); + renderer.setCurrentTown("VisualForecast 1000"); + + BufferedImage disposableImage = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY); + Graphics2D g = disposableImage.createGraphics(); + FontMetrics f = g.getFontMetrics(font); + String[] lines = test.split("\n"); + for (int i = 0; i < lines.length; i++) + { + splitText(lines[i], W - 150 - STROKE_WIDTH, f, linesList); + //if(words[i]) + } + + g.dispose(); + disposableImage.flush(); + + redrawRegionlost(renderer); + + this.lines = linesList.toArray(new String[0]); + } + + private void splitText(String text, int maxWidth, FontMetrics fontMetrics, ArrayList lineList) { + + text = text.trim(); + //this.text.add(text); + if (fontMetrics.stringWidth(text) <= maxWidth) + lineList.add(text); + else + { + while (text.length() > 0) + { + int idx = binSearch(text, fontMetrics, maxWidth, 1, text.length()); + if (idx <= 0) + break; + String texPart = text.substring(0, idx).trim(); + if (fontMetrics.stringWidth(text.substring(0, Math.min(text.length(), idx + 1))) > maxWidth && texPart.contains(" ")) + { + idx = texPart.lastIndexOf(" "); + texPart = texPart.substring(0, idx); + } + lineList.add(texPart); + text = text.substring(idx).trim(); + } + } + } + + private static int binSearch(String string, FontMetrics fontMetrics, int key, int low, int high) { + int index = 0; + + while (low <= high) + { + int mid = low + ((high - low) / 2); + int midmetric = fontMetrics.stringWidth(string.substring(0, mid)); + if (midmetric < key) + { + low = mid + 1; + index = mid; + } + else if (midmetric > key) + { + high = mid - 1; + } + else if (midmetric == key) + { + index = mid; + break; + } + } + return index; + } + + @Override + public void drawDisplay(RenderPanel renderer, Graphics2D g2d, long ticks, int iconTicks) { + } + + @Override + public void drawBoundLimitedDisplay(RenderPanel renderer, Graphics2D g2d, Rectangle bounds, long ticks, int iconTicks) { + DrawingUtil.drawGradientRect(g2d, 60, TOPBAR_HEIGHT, W - 120, MAINBAR_HEIGHT, 20, BG_BLUE.darker(), BG_BLUE.brighter()); + g2d.setFont(font); + FontMetrics fontMetrics = g2d.getFontMetrics(); + g2d.setColor(BG_BLUE.darker()); + g2d.drawRect(60 + STROKE_OFFSET, TOPBAR_HEIGHT + STROKE_OFFSET, W - 120 - STROKE_WIDTH, MAINBAR_HEIGHT - STROKE_WIDTH); + for (int i = 0; i < Math.min(9, lines.length - this.stringOffset); i++) + { + if (lines[i + stringOffset].startsWith("*") && lines[i].endsWith("*")) + { + String line = lines[i + stringOffset].substring(1, lines[i + stringOffset].length() - 1); + DrawingUtil.drawOutlinedString(g2d, 60 + 20 + STROKE_OFFSET, TOPBAR_HEIGHT + STROKE_OFFSET + 40 + i * 30, "*", Color.WHITE, Color.BLACK, 2); + DrawingUtil.drawOutlinedString(g2d, 60 + 20 + STROKE_OFFSET + (W - 150 - STROKE_WIDTH) / 2 - fontMetrics.stringWidth(line) / 2, TOPBAR_HEIGHT + STROKE_OFFSET + 40 + i * 30, line, Color.WHITE, Color.BLACK, 2); + DrawingUtil.drawOutlinedString(g2d, W - 95 - STROKE_WIDTH, TOPBAR_HEIGHT + STROKE_OFFSET + 40 + i * 30, "*", Color.WHITE, Color.BLACK, 2); + } + else if (lines[i + stringOffset].startsWith("[") && lines[i].endsWith("]")) + { + String line = lines[i + stringOffset].substring(1, lines[i + stringOffset].length() - 1); + DrawingUtil.drawOutlinedString(g2d, 60 + 20 + STROKE_OFFSET + (W - 150 - STROKE_WIDTH) / 2 - fontMetrics.stringWidth(line) / 2, TOPBAR_HEIGHT + STROKE_OFFSET + 40 + i * 30, line, Color.WHITE, Color.BLACK, 2); + } + else DrawingUtil.drawOutlinedString(g2d, 60 + 20 + STROKE_OFFSET, TOPBAR_HEIGHT + STROKE_OFFSET + 40 + i * 30, lines[i + stringOffset], Color.WHITE, Color.BLACK, 2); + } + } + + @Override + public void redrawRegionlost(RenderPanel renderer) { + renderer.addRedrawBound(TOPBAR_HEIGHT, W - 120, MAINBAR_HEIGHT, 20); + } + + @Override + public void notifyForecastProviderUpdate(RenderPanel renderer, ForecastProvider forecastProvider) { + } + +} diff --git a/src/com/flaremicro/visualforecast/displays/DisplayFactory.java b/src/com/flaremicro/visualforecast/displays/DisplayFactory.java new file mode 100644 index 0000000..2ae42fc --- /dev/null +++ b/src/com/flaremicro/visualforecast/displays/DisplayFactory.java @@ -0,0 +1,32 @@ +package com.flaremicro.visualforecast.displays; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.flaremicro.visualforecast.PropertyManager; + +public class DisplayFactory { + int currentIndex = -1; + String indexValue = ""; + + private List nameOrder = new ArrayList(); + private Map displays = new HashMap(); + + public DisplayFactory(PropertyManager propertyManager) + { + displays.put("message", new MessageForecastDisplay()); + displays.put("day", new DayForecastDisplay()); + displays.put("hourly", new HourlyForecastDisplay()); + nameOrder.add("message"); + nameOrder.add("day"); + nameOrder.add("hourly"); + } + + public Display nextDisplay() { + currentIndex = (currentIndex + 1) % nameOrder.size(); + return displays.get(nameOrder.get(currentIndex)); + } + +} diff --git a/src/com/flaremicro/visualforecast/displays/HourlyForecastDisplay.java b/src/com/flaremicro/visualforecast/displays/HourlyForecastDisplay.java new file mode 100644 index 0000000..7e9a18d --- /dev/null +++ b/src/com/flaremicro/visualforecast/displays/HourlyForecastDisplay.java @@ -0,0 +1,349 @@ +package com.flaremicro.visualforecast.displays; + +import static com.flaremicro.visualforecast.graphics.RenderConstants.BG_BLUE; +import static com.flaremicro.visualforecast.graphics.RenderConstants.MAINBAR_HEIGHT; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.GradientPaint; +import java.awt.Graphics2D; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.Transparency; +import java.awt.image.BufferedImage; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; + +import com.flaremicro.visualforecast.RenderPanel; +import com.flaremicro.visualforecast.api.ForecastProvider; +import com.flaremicro.visualforecast.forecast.DayForecast; +import com.flaremicro.visualforecast.forecast.ForecastDetails; +import com.flaremicro.visualforecast.forecast.HourlyForecast; +import com.flaremicro.visualforecast.forecast.TownForecast; +import com.flaremicro.visualforecast.forecast.ValueCheck; +import com.flaremicro.visualforecast.graphics.DrawingUtil; +import com.flaremicro.visualforecast.graphics.FontManager; +import com.flaremicro.visualforecast.graphics.RenderConstants; +import com.flaremicro.visualforecast.icons.IconProvider; + +public class HourlyForecastDisplay implements Display { + private Font font; + private Font smallFont; + private ForecastDetails details; + private TownForecast currentTown = null; + private int townIndex; + + private int ticksBeforeChange = 200; + private int animationTicks = -1; + + private byte[] windchill = null; + private byte[] tempval = null; + private float[] percipval = null; + + DateFormat simpleDateFormat = new SimpleDateFormat("ha"); + + BufferedImage lastBuffer = null; + + public HourlyForecastDisplay() { + font = FontManager.getInstance().getOrCreateFont(Font.TRUETYPE_FONT, this.getClass().getResource("/Star4000.ttf")); + smallFont = FontManager.getInstance().getOrCreateFont(Font.TRUETYPE_FONT, this.getClass().getResource("/Star4000 Small.ttf")); + } + + @Override + public void tick(RenderPanel renderer, long ticks, int iconTicks) { + ticksBeforeChange--; + if (ticksBeforeChange <= 0) + { + ticksBeforeChange = 200; + + do + { + townIndex++; + if (details == null || townIndex >= details.getTownForecast().length) + { + renderer.nextDisplay(); + return; + } + this.currentTown = details.getTownForecast()[townIndex]; + } + while (this.currentTown == null || currentTown.getHourlyForecast() == null || currentTown.getHourlyForecast().length == 0); + renderer.setCurrentTown(currentTown.getTownName()); + setGraphValues(currentTown); + renderer.requestFullRepaint(); + } + } + + public void setGraphValues(TownForecast town) { + if (town != null) + { + HourlyForecast[] forecast = town.getHourlyForecast(); + if (forecast != null && forecast.length > 0) + { + int size = Math.min(12, forecast.length); + windchill = new byte[size]; + tempval = new byte[size]; + percipval = new float[size]; + for (int i = 0; i < size; i++) + { + windchill[i] = forecast[i].windChill; + tempval[i] = forecast[i].temp; + percipval[i] = forecast[i].percip; + } + } + } + } + + @Override + public void initDisplay(RenderPanel renderer, ForecastProvider forecastProvider, long ticks, int iconTicks) { + this.details = forecastProvider != null ? forecastProvider.getForecast() : null; + renderer.setCurrentForecast("12 Hour Forecast"); + if (details == null || details.getTownForecast() == null || details.getTownForecast().length <= 0) + this.details = null; + else + { + townIndex = 0; + currentTown = details.getTownForecast()[townIndex]; + renderer.setCurrentTown(currentTown.getTownName()); + setGraphValues(currentTown); + } + redrawRegionlost(renderer); + } + + @Override + public void drawDisplay(RenderPanel renderer, Graphics2D g2d, long ticks, int iconTicks) { + HourlyForecast[] forecast = currentTown.getHourlyForecast(); + if (forecast == null) + return; + + g2d.setColor(RenderConstants.BG_BLUE); + //g2d.fillRect(RenderConstants.SIDE_OFFSET, RenderConstants.TOPBAR_HEIGHT + 20, RenderConstants.W - RenderConstants.SIDE_OFFSET * 2, RenderConstants.MAINBAR_HEIGHT - 40); + + DrawingUtil.drawGradientRect(g2d, RenderConstants.SIDE_OFFSET, RenderConstants.TOPBAR_HEIGHT + 20, RenderConstants.W - RenderConstants.SIDE_OFFSET * 2, RenderConstants.MAINBAR_HEIGHT - 40, 15, BG_BLUE.darker(), BG_BLUE.brighter()); + + int min = minTemp(forecast); + int max = maxTemp(forecast); + + int range = max - min; + + if (range == 0) + { + min -= 1; + max += 1; + range = 2; + } + + g2d.setPaint(new GradientPaint(0, RenderConstants.TOPBAR_HEIGHT + 40, RenderConstants.BG_ORANGE, 0, RenderConstants.MAINBAR_HEIGHT, RenderConstants.BG_PURPLE)); + g2d.fillRect(RenderConstants.SIDE_OFFSET + 40, RenderConstants.TOPBAR_HEIGHT + 40, RenderConstants.W - RenderConstants.SIDE_OFFSET * 2 - 60, RenderConstants.MAINBAR_HEIGHT - 124); + + g2d.setColor(Color.BLACK); + g2d.drawRect(RenderConstants.SIDE_OFFSET, RenderConstants.TOPBAR_HEIGHT + 20, RenderConstants.W - RenderConstants.SIDE_OFFSET * 2, RenderConstants.MAINBAR_HEIGHT - 40); + + /*float lineHeight = (RenderConstants.MAINBAR_HEIGHT - 104) / 9F; + for(int i = 0; i < 10; i++) + { + g2d.drawLine(RenderConstants.SIDE_OFFSET + 36, (int)(RenderConstants.TOPBAR_HEIGHT + 40 + lineHeight * i), RenderConstants.W - RenderConstants.SIDE_OFFSET - 36, (int) (RenderConstants.TOPBAR_HEIGHT + 40 + lineHeight * i)); + }*/ + g2d.setFont(smallFont.deriveFont(20F)); + FontMetrics fm = g2d.getFontMetrics(); + + DrawingUtil.drawOutlinedString(g2d, 100, RenderConstants.TOPBAR_HEIGHT + 35, "Temperature\u00B0c", Color.RED, Color.BLACK, 2); + DrawingUtil.drawOutlinedString(g2d, 260, RenderConstants.TOPBAR_HEIGHT + 35, "Wind Chill\u00B0c", Color.WHITE, Color.BLACK, 2); + DrawingUtil.drawOutlinedString(g2d, 410, RenderConstants.TOPBAR_HEIGHT + 35, "Precipitation%", Color.CYAN, Color.BLACK, 2); + + g2d.setFont(font.deriveFont(20F)); + String minText = min + "\u00B0"; + String maxText = max + "\u00B0"; + + DrawingUtil.drawOutlinedString(g2d, RenderConstants.SIDE_OFFSET + 38 - fm.stringWidth(maxText), RenderConstants.TOPBAR_HEIGHT + 53, maxText, Color.YELLOW, Color.BLACK, 2); + DrawingUtil.drawOutlinedString(g2d, RenderConstants.SIDE_OFFSET + 38 - fm.stringWidth(minText), RenderConstants.TOPBAR_HEIGHT + MAINBAR_HEIGHT - 92, minText, Color.YELLOW, Color.BLACK, 2); + + g2d.setFont(font.deriveFont(20F)); + g2d.setColor(RenderConstants.BG_ORANGE.darker()); + g2d.drawLine(RenderConstants.SIDE_OFFSET + 40, (RenderConstants.TOPBAR_HEIGHT + 50), (RenderConstants.W - RenderConstants.SIDE_OFFSET - 20), (RenderConstants.TOPBAR_HEIGHT + 50)); + + g2d.setColor(RenderConstants.BG_PURPLE.brighter()); + g2d.drawLine(RenderConstants.SIDE_OFFSET + 40, RenderConstants.TOPBAR_HEIGHT + 50 + RenderConstants.MAINBAR_HEIGHT - 144, (RenderConstants.W - RenderConstants.SIDE_OFFSET - 20), RenderConstants.TOPBAR_HEIGHT + 50 + RenderConstants.MAINBAR_HEIGHT - 144); + + //BlackLines + + g2d.setFont(smallFont.deriveFont(20F)); + if (max > 0 && min < 0) + { + int nextTemp = (RenderConstants.TOPBAR_HEIGHT + RenderConstants.MAINBAR_HEIGHT - 94) + (int) (((min) / (float) range) * (RenderConstants.MAINBAR_HEIGHT - 144)); + g2d.setColor(Color.LIGHT_GRAY); + g2d.drawLine(RenderConstants.SIDE_OFFSET + 40, nextTemp, RenderConstants.W - RenderConstants.SIDE_OFFSET - 20, nextTemp); + DrawingUtil.drawOutlinedString(g2d, RenderConstants.SIDE_OFFSET + 40 - fm.stringWidth("0\u00B0"), nextTemp + 4, "0\u00B0", Color.YELLOW, Color.BLACK, 2); + } + + int slotWidth = (RenderConstants.W - RenderConstants.SIDE_OFFSET * 2 - 60) / 12; + + g2d.setClip(RenderConstants.SIDE_OFFSET + 40, RenderConstants.TOPBAR_HEIGHT + 40, RenderConstants.W - RenderConstants.SIDE_OFFSET * 2 - 60, RenderConstants.MAINBAR_HEIGHT - 124); + + g2d.translate(RenderConstants.SIDE_OFFSET + 40, RenderConstants.TOPBAR_HEIGHT + RenderConstants.MAINBAR_HEIGHT - 94); + + g2d.translate(2, 2); + g2d.setColor(Color.BLACK); + this.graphTemp(g2d, this.windchill, min, slotWidth, range, RenderConstants.MAINBAR_HEIGHT - 144); + g2d.setColor(Color.WHITE); + g2d.translate(-2, -2); + this.graphTemp(g2d, this.windchill, min, slotWidth, range, RenderConstants.MAINBAR_HEIGHT - 144); + + g2d.translate(2, 2); + g2d.setColor(Color.BLACK); + this.graphPercent(g2d, this.percipval, slotWidth, 100F, RenderConstants.MAINBAR_HEIGHT - 144); + g2d.setColor(Color.CYAN); + g2d.translate(-2, -2); + this.graphPercent(g2d, this.percipval, slotWidth, 100F, RenderConstants.MAINBAR_HEIGHT - 144); + + g2d.translate(2, 2); + g2d.setColor(Color.BLACK); + this.graphTemp(g2d, this.tempval, min, slotWidth, range, RenderConstants.MAINBAR_HEIGHT - 144); + g2d.setColor(Color.RED); + g2d.translate(-2, -2); + this.graphTemp(g2d, this.tempval, min, slotWidth, range, RenderConstants.MAINBAR_HEIGHT - 144); + + g2d.translate(-RenderConstants.SIDE_OFFSET - 40, 94 - RenderConstants.TOPBAR_HEIGHT - RenderConstants.MAINBAR_HEIGHT); + + g2d.setClip(null); + + g2d.setStroke(new BasicStroke(2)); + g2d.setColor(Color.BLACK); + g2d.drawRect(RenderConstants.SIDE_OFFSET + 40, RenderConstants.TOPBAR_HEIGHT + 40, RenderConstants.W - RenderConstants.SIDE_OFFSET * 2 - 60, RenderConstants.MAINBAR_HEIGHT - 124); + + for (int i = 0; i < Math.min(12, forecast.length); i++) + { + String timeString = simpleDateFormat.format(forecast[i].hour); + timeString = timeString.substring(0, timeString.length() - 1); + if ((i % 2) == 0) + DrawingUtil.drawOutlinedString(g2d, RenderConstants.SIDE_OFFSET + 40 + slotWidth / 2 + (slotWidth * i) - fm.stringWidth(timeString) / 2, RenderConstants.TOPBAR_HEIGHT + MAINBAR_HEIGHT - 70, timeString, Color.YELLOW, Color.BLACK, 2); + else DrawingUtil.drawOutlinedString(g2d, RenderConstants.SIDE_OFFSET + 40 + slotWidth / 2 + (slotWidth * i) - fm.stringWidth(timeString) / 2, RenderConstants.TOPBAR_HEIGHT + MAINBAR_HEIGHT - 60, timeString, Color.YELLOW, Color.BLACK, 2); + IconProvider.drawIcon(g2d, IconProvider.INDEXED_ICONS[forecast[i].iconId], RenderConstants.SIDE_OFFSET + 45 + i * slotWidth, RenderConstants.TOPBAR_HEIGHT + MAINBAR_HEIGHT - 55, slotWidth - 10, iconTicks); + } + lastBuffer = renderer.getSnapshot().getSubimage(RenderConstants.SIDE_OFFSET - 1, RenderConstants.TOPBAR_HEIGHT + 19, RenderConstants.W - RenderConstants.SIDE_OFFSET * 2 + 2, RenderConstants.MAINBAR_HEIGHT - 38); + lastBuffer.setAccelerationPriority(1); + } + + public int minTemp(HourlyForecast[] forecast) { + int min = Integer.MAX_VALUE; + for (int i = 0; i < forecast.length; i++) + { + int currMin; + if (ValueCheck.valueNoData(forecast[i].windChill) && ValueCheck.valueNoData(forecast[i].temp)) + continue; + else if (ValueCheck.valueNoData(forecast[i].windChill)) + currMin = forecast[i].temp; + else if (ValueCheck.valueNoData(forecast[i].temp)) + currMin = forecast[i].windChill; + else currMin = Math.min(forecast[i].temp, forecast[i].windChill); + if (currMin < min) + min = currMin; + } + return min; + } + + public int maxTemp(HourlyForecast[] forecast) { + int max = Integer.MIN_VALUE; + for (int i = 0; i < forecast.length; i++) + { + int currMax; + if (ValueCheck.valueNoData(forecast[i].windChill) && ValueCheck.valueNoData(forecast[i].temp)) + continue; + else if (ValueCheck.valueNoData(forecast[i].windChill)) + currMax = forecast[i].temp; + else if (ValueCheck.valueNoData(forecast[i].temp)) + currMax = forecast[i].windChill; + else currMax = Math.max(forecast[i].temp, forecast[i].windChill); + if (currMax > max) + max = currMax; + } + return max; + } + + @Override + public void drawBoundLimitedDisplay(RenderPanel renderer, Graphics2D g2d, Rectangle bounds, long ticks, int iconTicks) { + HourlyForecast[] forecast = currentTown.getHourlyForecast(); + if (forecast == null) + return; + if (this.lastBuffer != null) + { + g2d.drawImage(lastBuffer, RenderConstants.SIDE_OFFSET - 1, RenderConstants.TOPBAR_HEIGHT + 19, renderer); + } + int slotWidth = (RenderConstants.W - RenderConstants.SIDE_OFFSET * 2 - 60) / 12; + for (int i = 0; i < Math.min(12, forecast.length); i++) + { + if (IconProvider.INDEXED_ICONS[forecast[i].iconId].isAnimated()) + IconProvider.drawIcon(g2d, IconProvider.INDEXED_ICONS[forecast[i].iconId], RenderConstants.SIDE_OFFSET + 45 + i * slotWidth, RenderConstants.TOPBAR_HEIGHT + MAINBAR_HEIGHT - 55, slotWidth - 10, iconTicks); + } + } + + public void graphTemp(Graphics2D g2d, byte[] tempVal, int min, int slotWidth, float range, float multiplier) { + int lastTemp = ValueCheck.NO_DATA_INT; + for (int i = 0; i < tempVal.length; i++) + { + if (ValueCheck.valueNoData(tempVal[i])) + { + lastTemp = ValueCheck.NO_DATA_INT; + continue; + } + int nextTemp = -(int) (((tempVal[i] - min) / range) * multiplier); + if (i == 0) + { + lastTemp = nextTemp; + } + if (ValueCheck.valueNoData(lastTemp)) + { + lastTemp = nextTemp; + continue; + } + g2d.drawLine(slotWidth / 2 + (slotWidth * (i - 1)), lastTemp, slotWidth / 2 + (slotWidth * i), nextTemp); + if (i == Math.min(12, tempVal.length) - 1) + { + g2d.drawLine(slotWidth / 2 + (slotWidth * (i)), nextTemp, slotWidth / 2 + (slotWidth * (i + 1)), nextTemp); + } + lastTemp = nextTemp; + } + } + + public void graphPercent(Graphics2D g2d, float[] precVal, int slotWidth, float range, float multiplier) { + int lastTemp = ValueCheck.NO_DATA_INT; + for (int i = 0; i < tempval.length; i++) + { + if (ValueCheck.valueNoData(precVal[i])) + { + lastTemp = ValueCheck.NO_DATA_INT; + continue; + } + int nextTemp = -(int) (((precVal[i]) / range) * multiplier); + if (i == 0) + { + lastTemp = nextTemp; + } + if (ValueCheck.valueNoData(lastTemp)) + { + lastTemp = nextTemp; + continue; + } + g2d.drawLine(slotWidth / 2 + (slotWidth * (i - 1)), lastTemp, slotWidth / 2 + (slotWidth * i), nextTemp); + if (i == Math.min(12, precVal.length) - 1) + { + g2d.drawLine(slotWidth / 2 + (slotWidth * (i)), nextTemp, slotWidth / 2 + (slotWidth * (i + 1)), nextTemp); + } + lastTemp = nextTemp; + } + } + + @Override + public void redrawRegionlost(RenderPanel renderer) { + int slotWidth = (RenderConstants.W - RenderConstants.SIDE_OFFSET * 2 - 60) / 12; + renderer.addRedrawBound(RenderConstants.SIDE_OFFSET + 45, RenderConstants.TOPBAR_HEIGHT + MAINBAR_HEIGHT - 55, slotWidth * 12 - 10, slotWidth - 10); + } + + @Override + public void notifyForecastProviderUpdate(RenderPanel renderer, ForecastProvider forecastProvider) { + + } +} diff --git a/src/com/flaremicro/visualforecast/displays/MessageForecastDisplay.java b/src/com/flaremicro/visualforecast/displays/MessageForecastDisplay.java new file mode 100644 index 0000000..8909c99 --- /dev/null +++ b/src/com/flaremicro/visualforecast/displays/MessageForecastDisplay.java @@ -0,0 +1,175 @@ +package com.flaremicro.visualforecast.displays; + +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.util.ArrayList; + +import com.flaremicro.visualforecast.RenderPanel; +import com.flaremicro.visualforecast.api.ForecastProvider; +import com.flaremicro.visualforecast.graphics.DrawingUtil; +import com.flaremicro.visualforecast.graphics.FontManager; + +import static com.flaremicro.visualforecast.graphics.RenderConstants.*; + +public class MessageForecastDisplay implements Display { + + private String[] lines = new String[0]; + private Font font; + + private int stringOffset = 0; + private int ticksBeforeChange = 0; + + public MessageForecastDisplay() { + font = FontManager.getInstance().getOrCreateFont(Font.TRUETYPE_FONT, this.getClass().getResource("/Star4000.ttf")).deriveFont(30F); + + /* + int w2 = g2d.getFontMetrics().stringWidth("EXTREME WEATHER ADVISORY"); + drawOutlinedString(g2d, (W >> 1) - (w2 >> 1), TOPBAR_HEIGHT + 48, "EXTREME WEATHER ADVISORY", Color.RED, Color.BLACK, 2); + + g2d.setFont(font.deriveFont(30F)); + for (int i = 0; i < testString.length; i++) + { + drawOutlinedString(g2d, 90, TOPBAR_HEIGHT + 78 + 25 * i, testString[i], Color.WHITE, Color.BLACK, 1); + } + */ + } + + @Override + public void tick(RenderPanel renderer, long ticks, int iconTicks) { + ticksBeforeChange--; + if (ticksBeforeChange <= 0) + { + stringOffset += 9; + if (stringOffset < lines.length) + { + ticksBeforeChange = 200; + renderer.requestExclusiveBoundedRepaint(); + } + else + { + renderer.nextDisplay(); + } + } + } + + @Override + public void initDisplay(RenderPanel renderer, ForecastProvider forecastProvider, long ticks, int iconTicks) { + ticksBeforeChange = 200; + stringOffset = 0; + String test = "*IMPORTANT NOTICE*\n*THIS BETA IS UNFINISHED*\n\nThis is a Beta version of the VisualForeast 1000! Many things are not finished, and only the hourly and 7 day forecasts are ready. There will be a lot more to come!\nPlease note that there are some issues with data collection from Environment Canada resulting in missing or potentially incorrect information around midnight hours. This will be fixed as the project continues and the disclaimer will be removed."; + ArrayList linesList = new ArrayList(); + + renderer.setCurrentForecast("Welcome to the"); + renderer.setCurrentTown("VisualForecast 1000"); + + BufferedImage disposableImage = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY); + Graphics2D g = disposableImage.createGraphics(); + FontMetrics f = g.getFontMetrics(font); + String[] lines = test.split("\n"); + for (int i = 0; i < lines.length; i++) + { + splitText(lines[i], W - 150 - STROKE_WIDTH, f, linesList); + //if(words[i]) + } + + g.dispose(); + disposableImage.flush(); + + redrawRegionlost(renderer); + + this.lines = linesList.toArray(new String[0]); + } + + private void splitText(String text, int maxWidth, FontMetrics fontMetrics, ArrayList lineList) { + + text = text.trim(); + //this.text.add(text); + if (fontMetrics.stringWidth(text) <= maxWidth) + lineList.add(text); + else + { + while (text.length() > 0) + { + int idx = binSearch(text, fontMetrics, maxWidth, 1, text.length()); + if (idx <= 0) + break; + String texPart = text.substring(0, idx).trim(); + if (fontMetrics.stringWidth(text.substring(0, Math.min(text.length(), idx + 1))) > maxWidth && texPart.contains(" ")) + { + idx = texPart.lastIndexOf(" "); + texPart = texPart.substring(0, idx); + } + lineList.add(texPart); + text = text.substring(idx).trim(); + } + } + } + + private static int binSearch(String string, FontMetrics fontMetrics, int key, int low, int high) { + int index = 0; + + while (low <= high) + { + int mid = low + ((high - low) / 2); + int midmetric = fontMetrics.stringWidth(string.substring(0, mid)); + if (midmetric < key) + { + low = mid + 1; + index = mid; + } + else if (midmetric > key) + { + high = mid - 1; + } + else if (midmetric == key) + { + index = mid; + break; + } + } + return index; + } + + @Override + public void drawDisplay(RenderPanel renderer, Graphics2D g2d, long ticks, int iconTicks) { + } + + @Override + public void drawBoundLimitedDisplay(RenderPanel renderer, Graphics2D g2d, Rectangle bounds, long ticks, int iconTicks) { + DrawingUtil.drawGradientRect(g2d, 60, TOPBAR_HEIGHT, W - 120, MAINBAR_HEIGHT, 20, BG_BLUE.darker(), BG_BLUE.brighter()); + g2d.setFont(font); + FontMetrics fontMetrics = g2d.getFontMetrics(); + g2d.setColor(BG_BLUE.darker()); + g2d.drawRect(60 + STROKE_OFFSET, TOPBAR_HEIGHT + STROKE_OFFSET, W - 120 - STROKE_WIDTH, MAINBAR_HEIGHT - STROKE_WIDTH); + for (int i = 0; i < Math.min(9, lines.length - this.stringOffset); i++) + { + if (lines[i + stringOffset].startsWith("*") && lines[i].endsWith("*")) + { + String line = lines[i + stringOffset].substring(1, lines[i + stringOffset].length() - 1); + DrawingUtil.drawOutlinedString(g2d, 60 + 20 + STROKE_OFFSET, TOPBAR_HEIGHT + STROKE_OFFSET + 40 + i * 30, "*", Color.WHITE, Color.BLACK, 2); + DrawingUtil.drawOutlinedString(g2d, 60 + 20 + STROKE_OFFSET + (W - 150 - STROKE_WIDTH) / 2 - fontMetrics.stringWidth(line) / 2, TOPBAR_HEIGHT + STROKE_OFFSET + 40 + i * 30, line, Color.WHITE, Color.BLACK, 2); + DrawingUtil.drawOutlinedString(g2d, W - 95 - STROKE_WIDTH, TOPBAR_HEIGHT + STROKE_OFFSET + 40 + i * 30, "*", Color.WHITE, Color.BLACK, 2); + } + else if (lines[i + stringOffset].startsWith("[") && lines[i].endsWith("]")) + { + String line = lines[i + stringOffset].substring(1, lines[i + stringOffset].length() - 1); + DrawingUtil.drawOutlinedString(g2d, 60 + 20 + STROKE_OFFSET + (W - 150 - STROKE_WIDTH) / 2 - fontMetrics.stringWidth(line) / 2, TOPBAR_HEIGHT + STROKE_OFFSET + 40 + i * 30, line, Color.WHITE, Color.BLACK, 2); + } + else DrawingUtil.drawOutlinedString(g2d, 60 + 20 + STROKE_OFFSET, TOPBAR_HEIGHT + STROKE_OFFSET + 40 + i * 30, lines[i + stringOffset], Color.WHITE, Color.BLACK, 2); + } + } + + @Override + public void redrawRegionlost(RenderPanel renderer) { + renderer.addRedrawBound(TOPBAR_HEIGHT, W - 120, MAINBAR_HEIGHT, 20); + } + + @Override + public void notifyForecastProviderUpdate(RenderPanel renderer, ForecastProvider forecastProvider) { + } + +} diff --git a/src/com/flaremicro/visualforecast/displays/ThirtySixHourDisplay.java b/src/com/flaremicro/visualforecast/displays/ThirtySixHourDisplay.java new file mode 100644 index 0000000..bd57794 --- /dev/null +++ b/src/com/flaremicro/visualforecast/displays/ThirtySixHourDisplay.java @@ -0,0 +1,155 @@ +package com.flaremicro.visualforecast.displays; + +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.util.ArrayList; + +import com.flaremicro.visualforecast.RenderPanel; +import com.flaremicro.visualforecast.api.ForecastProvider; +import com.flaremicro.visualforecast.graphics.DrawingUtil; +import com.flaremicro.visualforecast.graphics.FontManager; + +import static com.flaremicro.visualforecast.graphics.RenderConstants.*; + +public class ThirtySixHourDisplay implements Display { + + private String[] lines = new String[0]; + private Font font; + + + public ThirtySixHourDisplay() { + font = FontManager.getInstance().getOrCreateFont(Font.TRUETYPE_FONT, this.getClass().getResource("/Star4000.ttf")).deriveFont(30F); + + /* + int w2 = g2d.getFontMetrics().stringWidth("EXTREME WEATHER ADVISORY"); + drawOutlinedString(g2d, (W >> 1) - (w2 >> 1), TOPBAR_HEIGHT + 48, "EXTREME WEATHER ADVISORY", Color.RED, Color.BLACK, 2); + + g2d.setFont(font.deriveFont(30F)); + for (int i = 0; i < testString.length; i++) + { + drawOutlinedString(g2d, 90, TOPBAR_HEIGHT + 78 + 25 * i, testString[i], Color.WHITE, Color.BLACK, 1); + } + */ + } + + @Override + public void tick(RenderPanel renderer, long ticks, int iconTicks) { + } + + @Override + public void initDisplay(RenderPanel renderer, ForecastProvider forecastProvider, long ticks, int iconTicks) { + String test = "*IMPORTANT NOTICE*\n*POWER OUTAGE POSSIBLE*\n*DUE TO EXTREME WINDS*\n\nHurricane-force winds capable of reaching 127km/h will be hitting the east coast around noon tomorrow. Keep windows and doors barricaded to prevent"; + ArrayList linesList = new ArrayList(); + + BufferedImage disposableImage = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY); + Graphics2D g = disposableImage.createGraphics(); + FontMetrics f = g.getFontMetrics(font); + System.out.println("yas2"); + String[] lines = test.split("\n"); + for(int i = 0; i < lines.length; i++) + { + splitText(lines[i], W - 150 - STROKE_WIDTH, f, linesList); + //if(words[i]) + } + + g.dispose(); + disposableImage.flush(); + + System.out.println("yas"); + + this.lines = linesList.toArray(new String[0]); + } + + private void splitText(String text, int maxWidth, FontMetrics fontMetrics, ArrayList lineList) { + + text = text.trim(); + //this.text.add(text); + if (fontMetrics.stringWidth(text) <= maxWidth) + lineList.add(text); + else + { + while (text.length() > 0) + { + int idx = binSearch(text, fontMetrics, maxWidth, 1, text.length()); + if (idx <= 0) + break; + String texPart = text.substring(0, idx).trim(); + if (fontMetrics.stringWidth(text.substring(0, Math.min(text.length(), idx + 1))) > maxWidth && texPart.contains(" ")) + { + idx = texPart.lastIndexOf(" "); + texPart = texPart.substring(0, idx); + } + lineList.add(texPart); + text = text.substring(idx).trim(); + } + } + } + + private static int binSearch(String string, FontMetrics fontMetrics, int key, int low, int high) { + int index = 0; + + while (low <= high) + { + int mid = low + ((high - low) / 2); + int midmetric = fontMetrics.stringWidth(string.substring(0, mid)); + if (midmetric < key) + { + low = mid + 1; + index = mid; + } + else if (midmetric > key) + { + high = mid - 1; + } + else if (midmetric == key) + { + index = mid; + break; + } + } + return index; + } + + @Override + public void drawDisplay(RenderPanel renderer, Graphics2D g2d, long ticks, int iconTicks) { + } + + @Override + public void drawBoundLimitedDisplay(RenderPanel renderer, Graphics2D g2d, Rectangle bounds, long ticks, int iconTicks) { + DrawingUtil.drawGradientRect(g2d, 60, TOPBAR_HEIGHT, W - 120, MAINBAR_HEIGHT, 20, BG_BLUE.brighter(), BG_BLUE.darker()); + g2d.setFont(font); + FontMetrics fontMetrics = g2d.getFontMetrics(); + g2d.setColor(BG_BLUE.brighter()); + g2d.drawRect(60 + STROKE_OFFSET, TOPBAR_HEIGHT + STROKE_OFFSET, W - 120 - STROKE_WIDTH, MAINBAR_HEIGHT - STROKE_WIDTH); + for(int i = 0; i < lines.length; i++) + { + if(lines[i].startsWith("*") && lines[i].endsWith("*")) + { + String line = lines[i].substring(1, lines[i].length()-1); + DrawingUtil.drawOutlinedString(g2d, 60 + 20 + STROKE_OFFSET, TOPBAR_HEIGHT + STROKE_OFFSET + 40 + i*30, "*", Color.WHITE, Color.BLACK, 2); + DrawingUtil.drawOutlinedString(g2d, 60 + 20 + STROKE_OFFSET + (W - 150 - STROKE_WIDTH)/2 - fontMetrics.stringWidth(line)/2, TOPBAR_HEIGHT + STROKE_OFFSET + 40 + i*30, line, Color.WHITE, Color.BLACK, 2); + DrawingUtil.drawOutlinedString(g2d, W - 95 - STROKE_WIDTH, TOPBAR_HEIGHT + STROKE_OFFSET + 40 + i*30, "*", Color.WHITE, Color.BLACK, 2); + } + else if(lines[i].startsWith("[") && lines[i].endsWith("]")) + { + String line = lines[i].substring(1, lines[i].length()-1); + DrawingUtil.drawOutlinedString(g2d, 60 + 20 + STROKE_OFFSET + (W - 150 - STROKE_WIDTH)/2 - fontMetrics.stringWidth(line)/2, TOPBAR_HEIGHT + STROKE_OFFSET + 40 + i*30, line, Color.WHITE, Color.BLACK, 2); + } + else + DrawingUtil.drawOutlinedString(g2d, 60 + 20 + STROKE_OFFSET, TOPBAR_HEIGHT + STROKE_OFFSET + 40 + i*30, lines[i], Color.WHITE, Color.BLACK, 2); + } + } + + @Override + public void redrawRegionlost(RenderPanel renderer) { + } + + @Override + public void notifyForecastProviderUpdate(RenderPanel renderer, ForecastProvider forecastProvider) { + } + +} diff --git a/src/com/flaremicro/visualforecast/forecast/DayForecast.java b/src/com/flaremicro/visualforecast/forecast/DayForecast.java index 5a68f71..ab3c612 100644 --- a/src/com/flaremicro/visualforecast/forecast/DayForecast.java +++ b/src/com/flaremicro/visualforecast/forecast/DayForecast.java @@ -1,5 +1,7 @@ package com.flaremicro.visualforecast.forecast; +import com.flaremicro.visualforecast.icons.IconProvider; + public class DayForecast { public byte hiTemp; public byte loTemp; @@ -11,7 +13,7 @@ public class DayForecast { public DayForecast(byte hiTemp, byte loTemp, byte iconId, String weatherLine1, String weatherLine2, float percipPercent){ this.hiTemp = hiTemp; this.loTemp = loTemp; - this.iconId = (byte)(iconId & 63); + this.iconId = (byte)(iconId % IconProvider.INDEXED_ICONS.length); this.weatherLine1 = weatherLine1; this.weatherLine2 = weatherLine2; this.percipPercent = percipPercent; diff --git a/src/com/flaremicro/visualforecast/forecast/HourlyForecast.java b/src/com/flaremicro/visualforecast/forecast/HourlyForecast.java new file mode 100644 index 0000000..1f3c8ca --- /dev/null +++ b/src/com/flaremicro/visualforecast/forecast/HourlyForecast.java @@ -0,0 +1,24 @@ +package com.flaremicro.visualforecast.forecast; + +import java.util.Date; + +import com.flaremicro.visualforecast.icons.IconProvider; + +public class HourlyForecast { + public final Date hour; + public final byte iconId; + public final byte temp; + public final short windSpeed; + public final float percip; + public final byte windChill; + + public HourlyForecast(Date hour, byte iconId, byte temp, short windSpeed, float percip, byte windChill) + { + this.hour = hour; + this.iconId = (byte) (iconId % IconProvider.INDEXED_ICONS.length); + this.temp = temp; + this.windSpeed = windSpeed; + this.percip = percip; + this.windChill = windChill; + } +} diff --git a/src/com/flaremicro/visualforecast/forecast/TownForecast.java b/src/com/flaremicro/visualforecast/forecast/TownForecast.java index 553ff45..01adb88 100644 --- a/src/com/flaremicro/visualforecast/forecast/TownForecast.java +++ b/src/com/flaremicro/visualforecast/forecast/TownForecast.java @@ -1,18 +1,49 @@ package com.flaremicro.visualforecast.forecast; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + public class TownForecast { - public TownForecast(String townName, DayForecast[] dayForecast){ + public TownForecast(String townName, DayForecast[] dayForecast) { this.townName = townName; this.dayForecast = dayForecast; } - + private final String townName; private final DayForecast[] dayForecast; + private Set displays = new HashSet(); + + private HourlyForecast[] hourlyForecast; + + public boolean isDisplaySupported(String displayName) + { + return displays.size() == 0 || displays.contains(displayName); + } + + public void setSupportedDisplays(String ... displays){ + setSupportedDisplays(Arrays.asList(displays)); + } + + public void setSupportedDisplays(Collection displays){ + this.displays.clear(); + this.displays.addAll(displays); + } + + public void setHourlyForecast(HourlyForecast[] forecast) { + this.hourlyForecast = forecast; + } + + public HourlyForecast[] getHourlyForecast() { + return this.hourlyForecast; + } + public DayForecast[] getDayForecast() { return dayForecast; } - + public String getTownName() { return townName; } diff --git a/src/com/flaremicro/visualforecast/graphics/RenderConstants.java b/src/com/flaremicro/visualforecast/graphics/RenderConstants.java index 4e033d8..828faeb 100644 --- a/src/com/flaremicro/visualforecast/graphics/RenderConstants.java +++ b/src/com/flaremicro/visualforecast/graphics/RenderConstants.java @@ -31,6 +31,6 @@ public class RenderConstants { public static final Color BG_PURPLE = new Color(0x2b29b3); - public static final Color BG_OORANGE = new Color(0xff8c00); + public static final Color BG_ORANGE = new Color(0xff8c00); public static final Color BG_BLUE = new Color(0x394aa8); } diff --git a/vf1000.properties b/vf1000.properties index 871d714..a6c66df 100644 --- a/vf1000.properties +++ b/vf1000.properties @@ -1,3 +1,3 @@ #VisualForecast 1000 Properties file. Functional provider must be set for successful boot! -#Thu Mar 07 16:27:03 PST 2024 +#Fri Mar 15 19:46:22 PDT 2024 forecast-provider-jar=CanadaDatamartProvider.jar