diff --git a/README.md b/README.md index 40da397..174ee30 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ To run you will need: * Intel 8088 or compatible CPU * CGA compatible graphics card (EGA and VGA are backwards compatible) * A network interface (it is possible to use your machine's serial port with the EtherSLIP driver) -* A mouse +* A mouse is desirable but not 100% required ## Limitations * Text only (this may change in a later release) @@ -16,6 +16,15 @@ To run you will need: * No CSS or Javascript * Very long pages may be truncated if there is not enough RAM available +## Keyboard shortcuts +Escape : Exit +F2 : Invert screen (useful for LCD displays) +F6 / Ctrl+L : Select address bar +Tab / Shift+Tab : Cycle through selectable page elements +Backspace : Back in history + +The page position can be scrolled with cursor keys, Page up, Page down, Home and End + ## Network setup MicroWeb uses Michael Brutman's [mTCP networking library](http://www.brutman.com/mTCP/) for the network stack. You will need a DOS packet driver relevant to your network interface. You can read more about configuring DOS networking [here](http://www.brutman.com/Dos_Networking/dos_networking.html) diff --git a/src/DOS/CGA.cpp b/src/DOS/CGA.cpp index 27557e6..27c4425 100644 --- a/src/DOS/CGA.cpp +++ b/src/DOS/CGA.cpp @@ -413,7 +413,7 @@ bool CGADriver::ApplyScissor(int& y, int& height) } if (y + height >= scissorY2) { - height = scissorY2 - y - 1; + height = scissorY2 - y; } return true; } diff --git a/src/Interface.cpp b/src/Interface.cpp index 593d398..1f75348 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -28,11 +28,20 @@ AppInterface::AppInterface(App& inApp) : app(inApp) oldMouseY = -1; oldButtons = 0; textFieldCursorPosition = 0; + clickingButton = false; } void AppInterface::Reset() { oldPageHeight = 0; + if (activeWidget && !activeWidget->isInterfaceWidget) + { + activeWidget = NULL; + } + if (hoverWidget && !hoverWidget->isInterfaceWidget) + { + hoverWidget = NULL; + } } void AppInterface::DrawInterfaceWidgets() @@ -163,7 +172,7 @@ void AppInterface::Update() case KEYCODE_BACKSPACE: app.PreviousPage(); break; - case KEYCODE_CTRL_I: + case KEYCODE_F2: { Platform::input->HideMouse(); Platform::video->InvertScreen(); @@ -171,8 +180,17 @@ void AppInterface::Update() } break; case KEYCODE_CTRL_L: + case KEYCODE_F6: ActivateWidget(&addressBar); break; + + case KEYCODE_TAB: + CycleWidgets(1); + break; + case KEYCODE_SHIFT_TAB: + CycleWidgets(-1); + break; + default: // printf("%x\n", keyPress); break; @@ -243,6 +261,19 @@ void AppInterface::DeactivateWidget() { if (activeWidget) { + if (activeWidget->type == Widget::Button) + { + if (clickingButton) + { + app.renderer.InvertWidget(activeWidget); + } + else + { + Widget* widget = activeWidget; + activeWidget = NULL; + app.renderer.RedrawWidget(widget); + } + } if (activeWidget->type == Widget::TextField) { if (textFieldCursorPosition == -1) @@ -254,6 +285,10 @@ void AppInterface::DeactivateWidget() app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); } } + else if (activeWidget->GetLinkURL()) + { + InvertWidgetsWithLinkURL(activeWidget->GetLinkURL()); + } activeWidget = NULL; } } @@ -272,8 +307,14 @@ void AppInterface::ActivateWidget(Widget* widget) switch (activeWidget->type) { case Widget::Button: - activeWidget = hoverWidget; - app.renderer.InvertWidget(activeWidget); + if (clickingButton) + { + app.renderer.InvertWidget(activeWidget); + } + else + { + app.renderer.RedrawWidget(activeWidget); + } break; case Widget::TextField: if (activeWidget == &addressBar && strlen(activeWidget->textField->buffer) > 0) @@ -287,9 +328,31 @@ void AppInterface::ActivateWidget(Widget* widget) app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); } break; + default: + if (widget->GetLinkURL()) + { + InvertWidgetsWithLinkURL(widget->GetLinkURL()); + app.renderer.SetStatus(URL::GenerateFromRelative(app.page.pageURL.url, widget->GetLinkURL()).url); + } + break; } } +} +void AppInterface::InvertWidgetsWithLinkURL(const char* url) +{ + for (int n = app.renderer.GetPageTopWidgetIndex(); n < app.page.numFinishedWidgets; n++) + { + Widget* pageWidget = &app.page.widgets[n]; + if (pageWidget->y > app.renderer.GetScrollPosition() + Platform::video->windowHeight) + { + break; + } + if (pageWidget->GetLinkURL() == url) + { + app.renderer.InvertWidget(pageWidget); + } + } } void AppInterface::HandleClick(int mouseX, int mouseY) @@ -339,6 +402,7 @@ void AppInterface::HandleClick(int mouseX, int mouseY) } else if (hoverWidget->type == Widget::Button) { + clickingButton = true; ActivateWidget(hoverWidget); } else if (hoverWidget->type == Widget::ScrollBar) @@ -364,28 +428,40 @@ void AppInterface::HandleClick(int mouseX, int mouseY) } } +void AppInterface::HandleButtonClicked(Widget* widget) +{ + if(widget->type == Widget::Button) + { + if (widget->button->form) + { + SubmitForm(widget->button->form); + } + else if (widget == &backButton) + { + app.PreviousPage(); + } + else if (widget == &forwardButton) + { + app.NextPage(); + } + } +} + void AppInterface::HandleRelease() { if (activeWidget && activeWidget->type == Widget::Button) { - app.renderer.InvertWidget(activeWidget); - - if (hoverWidget == activeWidget) + if (clickingButton) { - if (activeWidget->button->form) - { - SubmitForm(activeWidget->button->form); - } - else if (activeWidget == &backButton) + if (hoverWidget == activeWidget) { - app.PreviousPage(); - } - else if (activeWidget == &forwardButton) - { - app.NextPage(); + HandleButtonClicked(activeWidget); } + DeactivateWidget(); + + activeWidget = NULL; + clickingButton = false; } - activeWidget = NULL; } else if (activeWidget == &scrollBar) { @@ -553,7 +629,22 @@ bool AppInterface::HandleActiveWidget(InputButtonCode keyPress) } break; case Widget::Button: - return true; + if (clickingButton) + { + return true; + } + if (keyPress == KEYCODE_ENTER) + { + HandleButtonClicked(activeWidget); + } + break; + default: + if (keyPress == KEYCODE_ENTER && activeWidget->GetLinkURL()) + { + app.OpenURL(URL::GenerateFromRelative(app.page.pageURL.url, activeWidget->GetLinkURL()).url); + return true; + } + break; } return false; @@ -633,3 +724,98 @@ void AppInterface::UpdatePageScrollBar() app.renderer.RedrawScrollBar(); } + +void AppInterface::CycleWidgets(int direction) +{ + if (app.page.numFinishedWidgets == 0) + { + return; + } + + int pageScrollBottom = app.renderer.GetScrollPosition() + Platform::video->windowHeight; + + // If there is an active widget, find index and check if it is on screen + int currentPos = -1; + if (activeWidget && !activeWidget->isInterfaceWidget) + { + for (int n = 0; n < app.page.numFinishedWidgets; n++) + { + Widget* widget = &app.page.widgets[n]; + if (activeWidget == widget) + { + if (app.renderer.GetScrollPosition() < widget->y + widget->height && pageScrollBottom > widget->y) + { + currentPos = n; + } + break; + } + } + } + + // If there is no active widget or not on screen, find the first widget on screen + if (currentPos == -1) + { + if (direction > 0) + { + for (currentPos = app.renderer.GetPageTopWidgetIndex(); currentPos < app.page.numFinishedWidgets; currentPos++) + { + Widget* widget = &app.page.widgets[currentPos]; + if (widget->y >= app.renderer.GetScrollPosition()) + { + break; + } + } + + if (currentPos == app.page.numFinishedWidgets) + { + currentPos = 0; + } + } + else + { + for (currentPos = app.page.numFinishedWidgets - 1; currentPos >= 0; currentPos--) + { + Widget* widget = &app.page.widgets[currentPos]; + if (widget->y + widget->height < pageScrollBottom) + { + break; + } + } + + if (currentPos < 0) + { + currentPos = app.page.numFinishedWidgets - 1; + } + } + } + + int startPos = currentPos; + + while (1) + { + Widget* widget = &app.page.widgets[currentPos]; + if (widget != activeWidget) + { + if ((widget->type == Widget::TextField) || (widget->type == Widget::Button) || (widget->GetLinkURL() && widget->GetLinkURL() != activeWidget->GetLinkURL())) + { + ActivateWidget(widget); + app.renderer.ScrollTo(widget); + return; + } + } + + currentPos += direction; + if (currentPos >= app.page.numFinishedWidgets) + { + currentPos = 0; + } + if (currentPos < 0) + { + currentPos = app.page.numFinishedWidgets - 1; + } + if (currentPos == startPos) + { + break; + } + } +} diff --git a/src/Interface.h b/src/Interface.h index 24bf488..dfbb8cc 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -34,6 +34,7 @@ class AppInterface void UpdatePageScrollBar(); Widget* GetActiveWidget() { return activeWidget; } + int GetTextFieldCursorPosition() { return textFieldCursorPosition; } Widget scrollBar; Widget addressBar; @@ -50,6 +51,9 @@ class AppInterface void HandleRelease(); bool HandleActiveWidget(InputButtonCode keyPress); void SubmitForm(WidgetFormData* form); + void CycleWidgets(int direction); + void InvertWidgetsWithLinkURL(const char* url); + void HandleButtonClicked(Widget* widget); void ActivateWidget(Widget* widget); void DeactivateWidget(); @@ -66,6 +70,7 @@ class AppInterface int scrollBarRelativeClickPositionY; int oldPageHeight; int textFieldCursorPosition; + bool clickingButton; ButtonWidgetData backButtonData; ButtonWidgetData forwardButtonData; diff --git a/src/KeyCodes.h b/src/KeyCodes.h index ef61538..8ad5d81 100644 --- a/src/KeyCodes.h +++ b/src/KeyCodes.h @@ -35,5 +35,18 @@ #define KEYCODE_BACKSPACE 8 #define KEYCODE_ENTER 13 -#define KEYCODE_CTRL_I 9 +#define KEYCODE_TAB 0x0009 +#define KEYCODE_SHIFT_TAB 0x0f00 + #define KEYCODE_CTRL_L 12 + +#define KEYCODE_F1 0x3b00 +#define KEYCODE_F2 0x3c00 +#define KEYCODE_F3 0x3d00 +#define KEYCODE_F4 0x3e00 +#define KEYCODE_F5 0x3f00 +#define KEYCODE_F6 0x4000 +#define KEYCODE_F7 0x4100 +#define KEYCODE_F8 0x4200 +#define KEYCODE_F9 0x4300 +#define KEYCODE_F10 0x4400 \ No newline at end of file diff --git a/src/Renderer.cpp b/src/Renderer.cpp index ee12e6d..50968a8 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -339,6 +339,10 @@ void Renderer::RenderWidgetInternal(Widget* widget, int baseY) if (widget->text->text) { Platform::video->DrawString(widget->text->text, widget->x, widget->y + baseY, widget->style.fontSize, widget->style.fontStyle); + if (app.ui.GetActiveWidget() && app.ui.GetActiveWidget()->GetLinkURL() && app.ui.GetActiveWidget()->GetLinkURL() == widget->GetLinkURL()) + { + Platform::video->InvertRect(widget->x, widget->y + baseY, widget->width, widget->height); + } } break; case Widget::HorizontalRule: @@ -347,7 +351,8 @@ void Renderer::RenderWidgetInternal(Widget* widget, int baseY) case Widget::Button: if (widget->button) { - DrawButtonRect(widget->x, widget->y + baseY, widget->width, widget->height); + bool isSelected = app.ui.GetActiveWidget() == widget; + DrawButtonRect(widget->x, widget->y + baseY, widget->width, widget->height, isSelected); Platform::video->DrawString(widget->button->text, widget->x + 8, widget->y + baseY + 2, widget->style.fontSize, widget->style.fontStyle); } break; @@ -379,6 +384,11 @@ void Renderer::RenderWidgetInternal(Widget* widget, int baseY) { *replaceChar = oldChar; } + + if (app.ui.GetActiveWidget() == widget) + { + DrawTextFieldCursorInternal(widget, app.ui.GetTextFieldCursorPosition(), false, baseY); + } } } break; @@ -467,7 +477,7 @@ void Renderer::SetTitle(const char* title) void Renderer::SetStatus(const char* status) { - if (status != oldStatus) + //if (!oldStatus || !status || strcmp(status, oldStatus)) { Platform::input->HideMouse(); @@ -482,12 +492,20 @@ void Renderer::SetStatus(const char* status) } } -void Renderer::DrawButtonRect(int x, int y, int width, int height) +void Renderer::DrawButtonRect(int x, int y, int width, int height, bool isSelected) { Platform::video->HLine(x + 1, y, width - 2); Platform::video->HLine(x + 1, y + height - 1, width - 2); Platform::video->VLine(x, y + 1, height - 2); Platform::video->VLine(x + width - 1, y + 1, height - 2); + + if (isSelected) + { + Platform::video->HLine(x + 1, y + 1, width - 2); + Platform::video->HLine(x + 1, y + height - 2, width - 2); + Platform::video->VLine(x + 1, y + 1, height - 2); + Platform::video->VLine(x + width - 2, y + 1, height - 2); + } } void Renderer::RenderWidget(Widget* widget) @@ -509,12 +527,8 @@ void Renderer::RedrawWidget(Widget* widget) EndWidgetDraw(); } -void Renderer::DrawTextFieldCursor(Widget* widget, int position, bool clear) +void Renderer::DrawTextFieldCursorInternal(Widget* widget, int position, bool clear, int baseY) { - if (!widget || !widget->textField || !widget->textField->buffer) - return; - - int x = widget->x + 2; int height = Platform::video->GetFont(1)->glyphHeight; @@ -530,8 +544,6 @@ void Renderer::DrawTextFieldCursor(Widget* widget, int position, bool clear) return; } - int baseY; - BeginWidgetDraw(widget, baseY); int y = widget->y + 2 + baseY; if (clear) @@ -542,6 +554,17 @@ void Renderer::DrawTextFieldCursor(Widget* widget, int position, bool clear) { Platform::video->VLine(x, y, height); } +} + +void Renderer::DrawTextFieldCursor(Widget* widget, int position, bool clear) +{ + if (!widget || !widget->textField || !widget->textField->buffer) + return; + + int baseY; + BeginWidgetDraw(widget, baseY); + + DrawTextFieldCursorInternal(widget, position, clear, baseY); EndWidgetDraw(); } @@ -553,6 +576,7 @@ void Renderer::BeginWidgetDraw(Widget* widget, int& baseY) if (!widget->isInterfaceWidget) { baseY = Platform::video->windowY - scrollPosition; + Platform::video->SetScissorRegion(upperRenderLine, lowerRenderLine); } else baseY = 0; } @@ -562,3 +586,12 @@ void Renderer::EndWidgetDraw() Platform::video->ClearScissorRegion(); Platform::input->ShowMouse(); } + +void Renderer::ScrollTo(Widget* widget) +{ + if (widget->y < app.renderer.GetScrollPosition() || widget->y + widget->height > app.renderer.GetScrollPosition() + Platform::video->windowHeight) + { + ScrollTo(widget->y - Platform::video->windowHeight / 2); + } +} + diff --git a/src/Renderer.h b/src/Renderer.h index 68a50bb..58ff49e 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -27,6 +27,7 @@ class Renderer void Update(); void Scroll(int delta); void ScrollTo(int position); + void ScrollTo(Widget* widget); void RedrawScrollBar(); int GetMaxScrollPosition(); @@ -44,9 +45,12 @@ class Renderer void RedrawModifiedTextField(Widget* widget, int position); void DrawTextFieldCursor(Widget* widget, int position, bool clear = false); + + int GetPageTopWidgetIndex() { return pageTopWidgetIndex; } private: - void DrawButtonRect(int x, int y, int width, int height); + void DrawButtonRect(int x, int y, int width, int height, bool isSelected = false); void RenderWidgetInternal(Widget* widget, int baseY); + void DrawTextFieldCursorInternal(Widget* widget, int position, bool clear, int baseY); void BeginWidgetDraw(Widget* widget, int& baseY); void EndWidgetDraw();