! Copyright (c) 2020 Jeffrey Armstrong ! ! Permission is hereby granted, free of charge, to any person obtaining a copy ! of this software and associated documentation files (the "Software"), to deal ! in the Software without restriction, including without limitation the rights ! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ! copies of the Software, and to permit persons to whom the Software is ! furnished to do so, subject to the following conditions: ! ! The above copyright notice and this permission notice shall be included in ! all copies or substantial portions of the Software. ! ! The Software shall be used for Good, not Evil. ! ! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ! SOFTWARE. module ag_render use render use appgraphics implicit none integer, parameter::default_width = 600 integer, parameter::default_height = 700 integer, parameter::default_font_size = 18 integer, parameter::max_links_displayed = 64 integer, volatile::ag_render_event integer, volatile::scroll_position integer, parameter::ag_render_event_none = 0 integer, parameter::ag_render_event_closed = 1 integer, parameter::ag_render_event_mouse = 2 integer, parameter::ag_render_event_back = 3 integer, parameter::ag_render_event_go = 4 integer, parameter::ag_render_event_scroll = 5 integer, parameter::ag_render_event_resize = 6 type :: link integer, dimension(4)::location character(1024)::url end type type, extends(renderer) :: appgraphics_renderer integer::window_id integer::address_id integer::go_button_id integer::back_button_id integer::scroll_id integer::font_size integer::default_font integer::line_spacing integer::left_border, right_border integer::bullet_margin integer::text_color integer::link_color integer::background_color integer::address_bar_height integer::scroll_bar_width integer::status_bar_height integer::link_count type(link), dimension(max_links_displayed)::links ! Needed to compute document height integer::initial_y contains procedure :: initialize => ag_initialize procedure :: new_page => ag_new_page procedure :: prepare_for_layout => ag_prepare_for_layout procedure :: text_width => ag_text_width procedure :: text_height => ag_text_height procedure :: is_text_visible => ag_text_visible procedure :: draw_porportional => ag_text_draw procedure :: preformatted_width => ag_preformatted_width procedure :: preformatted_height => ag_preformatted_height procedure :: is_preformatted_visible => ag_text_visible procedure :: draw_preformatted => ag_preformatted_draw procedure :: link_width => ag_link_width procedure :: link_height => ag_link_height procedure :: is_link_visible => ag_text_visible procedure :: draw_link => ag_link_draw procedure :: request_input => ag_request_input procedure :: draw_error => ag_draw_error procedure :: report_status => ag_draw_status procedure :: request_action => ag_action procedure :: request_save_filename => ag_request_save_filename procedure :: report_displayed_page => ag_report_page end type appgraphics_renderer contains subroutine window_closing_callback() use appgraphics, only: stopidle implicit none ag_render_event = ag_render_event_closed call stopidle() end subroutine window_closing_callback subroutine back_button_callback() use appgraphics, only: stopidle implicit none ag_render_event = ag_render_event_back call stopidle() end subroutine back_button_callback subroutine go_button_callback() use appgraphics, only: stopidle implicit none ag_render_event = ag_render_event_go call stopidle() end subroutine go_button_callback subroutine mouse_button_callback(x, y) use appgraphics, only: stopidle implicit none integer::x, y ag_render_event = ag_render_event_mouse call stopidle() end subroutine mouse_button_callback subroutine scrolled_callback(x) use appgraphics, only: stopidle implicit none integer::x scroll_position = x ag_render_event = ag_render_event_scroll call stopidle() end subroutine scrolled_callback subroutine resize_callback() use appgraphics, only: stopidle implicit none ag_render_event = ag_render_event_resize call stopidle() end subroutine resize_callback subroutine draw_address_bar(self) use appgraphics implicit none type(appgraphics_renderer)::self integer::x, address_width, label_x self%address_bar_height = 24 ! Set up some address bar colors call setfillstyle(SOLID_FILL, LIGHTGRAY) call setbkcolor(LIGHTGRAY) call setcolor(BLACK) call settextstyle(WINDOWS_FONT, HORIZ_DIR, 12) ! Draw the buttons first x = getmaxx()/4 - 40 if(self%back_button_id == 0) then self%back_button_id = createbutton(x, 2, 40, 20, "Back", back_button_callback) else call setbuttonposition(self%back_button_id, x, 2, 40, 20) end if x = x + 50 label_x = x x = x + textwidth("Address:") + 5 address_width = getmaxx()/2 - textwidth("Address:") - 5 if(self%address_id == 0) then self%address_id = createtextbox(x, 2, address_width, 20) else call settextboxposition(self%address_id, x, 2, address_width, 20) end if x = x + 10 + address_width if(self%go_button_id == 0) then self%go_button_id = createbutton(x, 2, 40, 20, "Go!", go_button_callback) else call setbuttonposition(self%go_button_id, x, 2, 40, 20) end if call setviewport(0, 0, getmaxx(), self%address_bar_height, .true.) call clearviewport() call outtextxy(label_x, 5, "Address:") call resetviewport() end subroutine draw_address_bar subroutine draw_status_bar(self, text) use appgraphics implicit none type(appgraphics_renderer)::self character(*), intent(in)::text self%status_bar_height = 16 call resetviewport() call setfillstyle(SOLID_FILL, LIGHTGRAY) call setbkcolor(LIGHTGRAY) call setcolor(BLACK) call settextstyle(WINDOWS_FONT, HORIZ_DIR, 12) call bar(0, getmaxy()-self%status_bar_height, getmaxx()+1, getmaxy()+1) call outtextxy(self%left_border+2, getmaxy()-self%status_bar_height+2, trim(text)) end subroutine draw_status_bar subroutine draw_scroll_bar(self) use appgraphics implicit none type(appgraphics_renderer)::self integer::h self%scroll_bar_width = 15 h = getmaxy()-self%address_bar_height-self%status_bar_height - 1 if(self%scroll_id == 0) then self%scroll_id = createscrollbar(getmaxx()-self%scroll_bar_width, & self%address_bar_height+1, & self%scroll_bar_width, & h, & SCROLL_VERTICAL, & scrolled_callback) call setscrollrange(self%scroll_id, 0, 100) else call setscrollbarposition(self%scroll_id, & getmaxx()-self%scroll_bar_width, & self%address_bar_height+1, & self%scroll_bar_width, & h) end if end subroutine draw_scroll_bar function compute_max_width(self) use appgraphics, only: getmaxx implicit none type(appgraphics_renderer)::self integer::compute_max_width compute_max_width = getmaxx() - self%left_border - & self%right_border - self%scroll_bar_width end function compute_max_width subroutine ag_initialize(self) use appgraphics implicit none class(appgraphics_renderer)::self self%window_id = initwindow(default_width, default_height, "LR-87", & DEFAULT_POSITION, DEFAULT_POSITION, & .FALSE., .FALSE.) call setwindowclosecallback(window_closing_callback) call registermousehandler(MOUSE_LB_UP, mouse_button_callback) call enableresize(resize_callback) call setbkcolor(LIGHTGRAY) call clearviewport() self%address_id = 0 self%go_button_id = 0 self%back_button_id = 0 self%scroll_id = 0 call draw_address_bar(self) call draw_status_bar(self, "Welcome to the LR-87 Gemini Client") call draw_scroll_bar(self) self%background_color = WHITE self%text_color = BLACK self%link_color = BLUE self%max_width = compute_max_width(self) self%font_size = default_font_size self%default_font = SERIF_FONT self%line_spacing = 2 self%left_border = 5 self%right_border = 5 self%bullet_margin = 5 end subroutine ag_initialize function get_clicked_link(self) use appgraphics implicit none class(appgraphics_renderer)::self integer::get_clicked_link integer::i, mx, my get_clicked_link = -1 mx = mousex() my = mousey() my = my - self%address_bar_height do i = 1, self%link_count if(my >= self%links(i)%location(2) .and. my <= self%links(i)%location(4) .and. & mx >= self%links(i)%location(1) .and. mx <= self%links(i)%location(3)) then get_clicked_link = i exit end if end do end function get_clicked_link subroutine ag_prepare_for_layout(self) use appgraphics implicit none class(appgraphics_renderer)::self call setbkcolor(self%background_color) call resetviewport() call setviewport(0, & self%address_bar_height+1, & getmaxx() - self%scroll_bar_width - 1, & getmaxy() - self%status_bar_height, & .true.) call clearviewport() self%link_count = 0 self%initial_y = self%y end subroutine ag_prepare_for_layout subroutine ag_new_page(self) implicit none class(appgraphics_renderer)::self self%y = 0 call setscrollposition(self%scroll_id, 0) ag_render_event = ag_render_event_none end subroutine ag_new_page pure function get_font_size(self, heading) implicit none class(appgraphics_renderer), intent(in)::self integer, intent(in)::heading integer::get_font_size get_font_size = self%font_size if(heading == 1) then get_font_size = 2*get_font_size else if(heading > 1) then get_font_size = (3*get_font_size)/2 end if end function get_font_size function ag_text_width(self, text, heading, list_item) use appgraphics implicit none class(appgraphics_renderer)::self character(*), intent(in)::text integer, intent(in), optional::heading logical, intent(in), optional::list_item integer::ag_text_width integer::font_size font_size = self%font_size if(present(heading)) then font_size = get_font_size(self, heading) end if call settextstyle(self%default_font, HORIZ_DIR, font_size) if(len_trim(text) > 0) then ag_text_width = textwidth(trim(text)) else ag_text_width = 0 end if ag_text_width = ag_text_width + self%left_border + self%right_border if(present(list_item)) then if(list_item) then ag_text_width = ag_text_width + self%font_size/2 + 2*self%bullet_margin end if end if end function ag_text_width function ag_text_height(self, text, heading, list_item) use appgraphics implicit none class(appgraphics_renderer)::self character(*), intent(in)::text integer, intent(in), optional::heading logical, intent(in), optional::list_item integer::ag_text_height integer::font_size font_size = self%font_size if(present(heading)) then font_size = get_font_size(self, heading) end if call settextstyle(self%default_font, HORIZ_DIR, font_size) if(len_trim(text) > 0) then ag_text_height = textheight(trim(text)) + self%line_spacing else ag_text_height = self%line_spacing + self%font_size end if end function ag_text_height function ag_text_visible(self, text) use appgraphics implicit none class(appgraphics_renderer)::self character(*), intent(in)::text logical::ag_text_visible ag_text_visible = (self%y > 0 .and. self%y < getmaxy()) end function ag_text_visible subroutine ag_text_draw(self, text, heading, list_item) use appgraphics implicit none class(appgraphics_renderer)::self character(*), intent(in)::text integer, intent(in), optional::heading logical, intent(in), optional::list_item integer::font_size integer::x, bx, by font_size = self%font_size if(present(heading)) then font_size = get_font_size(self, heading) end if x = self%left_border if(present(list_item)) then if(list_item) then bx = x + self%bullet_margin by = self%y + self%font_size/4 call setfillstyle(SOLID_FILL, self%text_color) call bar(bx, by, bx+self%font_size/2, by+self%font_size/2) x = bx + self%bullet_margin + self%font_size/2 end if end if call settextstyle(self%default_font, HORIZ_DIR, font_size) call setcolor(self%text_color) call outtextxy(x, self%y, trim(text)) end subroutine ag_text_draw function ag_link_width(self, text) use appgraphics implicit none class(appgraphics_renderer)::self character(*), intent(in)::text integer::ag_link_width ag_link_width = ag_text_width(self, text) end function ag_link_width function ag_link_height(self, text) use appgraphics implicit none class(appgraphics_renderer)::self character(*), intent(in)::text integer::ag_link_height ag_link_height = ag_text_height(self, text) end function ag_link_height subroutine ag_link_draw(self, text, url) implicit none class(appgraphics_renderer)::self character(*), intent(in)::text, url integer::text_width call settextstyle(self%default_font, HORIZ_DIR, self%font_size) call setcolor(self%link_color) call outtextxy(self%left_border, self%y, trim(text)) text_width = ag_link_width(self, trim(text)) call line(self%left_border, self%y + 7*self%font_size/8, & self%left_border + text_width, self%y + 7*self%font_size/8) ! Store this link self%link_count = self%link_count + 1 if(self%link_count < max_links_displayed) then self%links(self%link_count)%location(1) = self%left_border self%links(self%link_count)%location(2) = self%y self%links(self%link_count)%location(3) = self%left_border + text_width self%links(self%link_count)%location(4) = self%y + self%font_size self%links(self%link_count)%url = url end if end subroutine ag_link_draw function ag_preformatted_width(self, text) use appgraphics implicit none class(appgraphics_renderer)::self character(*), intent(in)::text integer::ag_preformatted_width call settextstyle(MONOSPACE_FONT, HORIZ_DIR, self%font_size) if(len_trim(text) > 0) then ag_preformatted_width = textwidth(trim(text)) else ag_preformatted_width = 0 end if ag_preformatted_width = ag_preformatted_width + self%left_border + self%right_border end function ag_preformatted_width function ag_preformatted_height(self, text) use appgraphics implicit none class(appgraphics_renderer)::self character(*), intent(in)::text integer::ag_preformatted_height call settextstyle(MONOSPACE_FONT, HORIZ_DIR, self%font_size) if(len_trim(text) > 0) then ag_preformatted_height = textheight(trim(text)) + self%line_spacing else ag_preformatted_height = self%font_size + self%line_spacing end if end function ag_preformatted_height subroutine ag_preformatted_draw(self, text) use appgraphics implicit none class(appgraphics_renderer)::self character(*), intent(in)::text call settextstyle(MONOSPACE_FONT, HORIZ_DIR, self%font_size) call setcolor(BLACK) call outtextxy(self%left_border, self%y, trim(text)) end subroutine ag_preformatted_draw function ag_request_input(self, question, answer) use appgraphics implicit none class(appgraphics_renderer)::self character(*), intent(in)::question character(*), intent(out)::answer logical::ag_request_input ag_request_input = dlgrequesttext(answer, "Input Requested", question) end function ag_request_input subroutine ag_draw_error(self, text) use appgraphics implicit none class(appgraphics_renderer)::self character(*), intent(in)::text call dlgmessage(0, text) end subroutine ag_draw_error subroutine ag_draw_status(self, text) implicit none class(appgraphics_renderer)::self character(*), intent(in)::text call draw_status_bar(self, text) end subroutine ag_draw_status function ag_action(self, text) use render, only: render_action_none, render_action_layout, & render_action_goto, render_action_quit, & idle_status implicit none class(appgraphics_renderer)::self character(*), intent(out)::text integer::ag_action integer::link_clicked integer::ignored ! For scrolling integer::doclength ! Resize can be laggy, so ensure it actually was finalized if(self%max_width /= compute_max_width(self)) then ag_render_event = ag_render_event_resize end if if(ag_render_event == ag_render_event_none) then call startidle(10000) end if ag_action = render_action_none select case (ag_render_event) case(ag_render_event_closed) ag_action = render_action_quit case(ag_render_event_back) ag_action = render_action_back case(ag_render_event_go) ignored = gettextboxcontents(self%address_id, text) ag_action = render_action_goto case(ag_render_event_mouse) link_clicked = get_clicked_link(self) if(link_clicked > 0) then text = self%links(link_clicked)%url ag_action = render_action_goto end if call clearmouseclick(MOUSE_LB_UP) case(ag_render_event_scroll) doclength = self%y - self%initial_y self%y = (doclength * scroll_position) / (-100) ag_action = render_action_layout case(ag_render_event_resize) ! Effectively have to redraw everything call draw_address_bar(self) call draw_status_bar(self, idle_status) call draw_scroll_bar(self) call setscrollposition(self%scroll_id, 0) ! Need to recompute the max width for the layout engine self%max_width = compute_max_width(self) self%y = 0 ag_action = render_action_layout end select ! We've handled the event here, reset to none ag_render_event = ag_render_event_none end function ag_action function ag_request_save_filename(self, url, mimetype, filename) use appgraphics implicit none class(appgraphics_renderer)::self character(*), intent(in)::url character(*), intent(in)::mimetype character(*), intent(out)::filename logical::ag_request_save_filename ag_request_save_filename = dlgsavefile(filename) end function ag_request_save_filename subroutine ag_report_page(self, text) use appgraphics implicit none class(appgraphics_renderer)::self character(*), intent(in)::text call setwindowtitle("LR-87 :: "//trim(text)) if(index(text, "gemini://") > 0) then call settextboxcontents(self%address_id, trim(text)) end if end subroutine ag_report_page end module ag_render